@camstack/addon-admin-ui 1.2.27 → 1.2.29

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 (106) hide show
  1. package/dist/assets/{AddLocationWizard-8cRW4Jzl.js → AddLocationWizard-f89-IkPt.js} +1 -1
  2. package/dist/assets/{AddonCollectionPage-CiaXvpIo.js → AddonCollectionPage-Ch2XRf5H.js} +1 -1
  3. package/dist/assets/Addons-CdHie4yk.js +1 -0
  4. package/dist/assets/{AdminTabs-Dqs2B5TP.js → AdminTabs-CMrAY8Sg.js} +1 -1
  5. package/dist/assets/{AiProfiles-B-ZYpQv6.js → AiProfiles-pTNZy3y1.js} +1 -1
  6. package/dist/assets/Automation-QfkBqrcI.js +29 -0
  7. package/dist/assets/{BrokerForm-BJMUS91J.js → BrokerForm-BAZ82UHs.js} +1 -1
  8. package/dist/assets/Capabilities-BU0m67L8.js +1 -0
  9. package/dist/assets/CapabilityBadges-Bd-jxf5M.js +1 -0
  10. package/dist/assets/{Cluster-DFBi0ryq.js → Cluster-Kz7sxkEh.js} +1 -1
  11. package/dist/assets/{ClusterRecordingTab-CZZq6Ou8.js → ClusterRecordingTab-1Ep0a4vW.js} +1 -1
  12. package/dist/assets/{Dashboard-LzquRrCy.js → Dashboard-BkqNzZ9k.js} +1 -1
  13. package/dist/assets/Data-VrvWqvck.js +1 -0
  14. package/dist/assets/DetectionIntelligence-DhvaWm4u.js +1 -0
  15. package/dist/assets/{DeviceAggregateSection-QGY3DOSD.js → DeviceAggregateSection-BjhiujPl.js} +1 -1
  16. package/dist/assets/{DeviceDetail-DcqxA5OP.js → DeviceDetail-CkQKBq03.js} +2 -2
  17. package/dist/assets/{Devices-hWiMd09n.js → Devices-D8-uaQ9P.js} +1 -1
  18. package/dist/assets/{EmbedPlayerPage-D2laK8qP.js → EmbedPlayerPage-VSLkL4EV.js} +1 -1
  19. package/dist/assets/Events-C2Pic1x_.js +1 -0
  20. package/dist/assets/{FormBuilder-9QasrqDp.js → FormBuilder-4LCVdSVV.js} +1 -1
  21. package/dist/assets/Identity-q7i7NqN9.js +1 -0
  22. package/dist/assets/IntegrationDetail-Dkxb4wOF.js +1 -0
  23. package/dist/assets/{Integrations-KpJqa2eh.js → Integrations-Bp74D4gz.js} +1 -1
  24. package/dist/assets/Integrations-Ds4j_JJ8.js +1 -0
  25. package/dist/assets/{Logs-DvIm0V8i.js → Logs-fg8GZafU.js} +1 -1
  26. package/dist/assets/{MotionZonesSettings-DMkPYwfm.js → MotionZonesSettings-DjXYc9mc.js} +1 -1
  27. package/dist/assets/{MyAccess-DQRxiDVc.js → MyAccess-COiq6WO3.js} +1 -1
  28. package/dist/assets/Network-CiqowSs4.js +1 -0
  29. package/dist/assets/{NodeAddonsSettingsPanel-mtSSN5di.js → NodeAddonsSettingsPanel-BGclM9Ra.js} +1 -1
  30. package/dist/assets/NodeInferenceDevices-DrRLe_-U.js +1 -0
  31. package/dist/assets/Notifications-DQQm9HiJ.js +1 -0
  32. package/dist/assets/Pipeline-BJAW6qZG.js +1 -0
  33. package/dist/assets/{Plates-CJZ3YLDf.js → Plates-CvGuC_Lp.js} +1 -1
  34. package/dist/assets/{PrivacyMaskSettings-DJbI6C5O.js → PrivacyMaskSettings-_kxexVKf.js} +1 -1
  35. package/dist/assets/{ProviderIcon-_3FEm0BG.js → ProviderIcon-wbxEq0bC.js} +1 -1
  36. package/dist/assets/Recordings-Nl54spnw.js +1 -0
  37. package/dist/assets/ScanNetworkModal-DeJtIRyh.js +1 -0
  38. package/dist/assets/{Settings-BrE-eHJV.js → Settings-DeEQKEal.js} +1 -1
  39. package/dist/assets/{Showroom-Bu1fPAOX.js → Showroom-CEeMdl7x.js} +1 -1
  40. package/dist/assets/{Terminal-C5p955Pa.js → Terminal-Bg-v89nq.js} +2 -2
  41. package/dist/assets/_virtual_mf-localSharedImportMap___mfe_internal__admin_ui_host-CDUWxK0N.js +1 -0
  42. package/dist/assets/{build-cluster-graph-yvmQsU2l.js → build-cluster-graph-Cv0zvtyF.js} +2 -2
  43. package/dist/assets/circle-check-big-CkPzYYPA.js +1 -0
  44. package/dist/assets/clock-J0G5s40X.js +1 -0
  45. package/dist/assets/{copy-BKONNj-k.js → copy-CL5WAyDW.js} +1 -1
  46. package/dist/assets/{debounced-invalidate-vPoYh4bP.js → debounced-invalidate-DCC0mRXL.js} +1 -1
  47. package/dist/assets/{dist-DvvMnEMz.js → dist-AGMCUw5A.js} +1 -1
  48. package/dist/assets/dist-B3duXlmo.js +7 -0
  49. package/dist/assets/{download-BXKxeM9W.js → download-_vQgHc_K.js} +1 -1
  50. package/dist/assets/{face-bbox-BIL9CxXo.js → face-bbox-BzRb0Cay.js} +1 -1
  51. package/dist/assets/hostInit-BvGX5XHF.js +1 -0
  52. package/dist/assets/index-BHm1FmD-.css +2 -0
  53. package/dist/assets/index-BeCIFexS.js +4 -0
  54. package/dist/assets/{key-round-DlIq6OT0.js → key-round-BLz8Hopw.js} +1 -1
  55. package/dist/assets/{mf-entry-bootstrap-0-f0799171.js → mf-entry-bootstrap-0-6ae366a5.js} +2 -2
  56. package/dist/assets/{pencil-Czmub7zF.js → pencil-omAkHegX.js} +1 -1
  57. package/dist/assets/{player-overlays-BBqyPKM2.js → player-overlays-DHswkbsp.js} +1 -1
  58. package/dist/assets/plus-BpyRM_02.js +1 -0
  59. package/dist/assets/{radio-Bsvrf66r.js → radio-DoX1V1g-.js} +1 -1
  60. package/dist/assets/{refresh-cw-DHug6qjw.js → refresh-cw-CE37ff37.js} +1 -1
  61. package/dist/assets/remoteEntry-Czt3qcfu.js +1 -0
  62. package/dist/assets/{rotate-ccw-mEW0yUja.js → rotate-ccw-DkNejMx0.js} +1 -1
  63. package/dist/assets/rotate-cw-DjzrA-is.js +1 -0
  64. package/dist/assets/search-BfmAjYfy.js +1 -0
  65. package/dist/assets/{send-BkJjp8Ci.js → send-BOE-EMzz.js} +1 -1
  66. package/dist/assets/{server-D502ug1V.js → server-Cmm-r2A6.js} +1 -1
  67. package/dist/assets/square-CC6Ki3FL.js +1 -0
  68. package/dist/assets/{square-check-big-oO6WcOKX.js → square-check-big-BAgeP4VI.js} +1 -1
  69. package/dist/assets/{src-Cus9kyK5.js → src-BuqU1Y2C.js} +10 -10
  70. package/dist/assets/src-d71rtvEQ.js +7 -0
  71. package/dist/assets/{upload-AHyvKWFD.js → upload-BgE7db3Z.js} +1 -1
  72. package/dist/assets/{useEventInvalidation-By-LiR-C.js → useEventInvalidation-Cw6wrN4q.js} +1 -1
  73. package/dist/assets/{virtual_mf-REMOTE_ENTRY_ID___mfe_internal__admin_ui_host__remoteEntry-_hash_-BX5F-03u.js → virtual_mf-REMOTE_ENTRY_ID___mfe_internal__admin_ui_host__remoteEntry-_hash_-BlNrk-4E.js} +2 -2
  74. package/dist/assets/{wifi-BqTNMokE.js → wifi-DfeLPHsk.js} +1 -1
  75. package/dist/assets/{zap-jkJVy5w-.js → zap-Bp4zqxqw.js} +1 -1
  76. package/dist/index.html +6 -6
  77. package/dist/server/addon.js +388 -236
  78. package/package.json +1 -1
  79. package/dist/assets/Addons-y8PCNUow.js +0 -1
  80. package/dist/assets/Capabilities-OzzB0e6-.js +0 -1
  81. package/dist/assets/CapabilityBadges-D2-uEkVq.js +0 -1
  82. package/dist/assets/Data-BH6j9g_-.js +0 -1
  83. package/dist/assets/DetectionIntelligence-DkrxY5iA.js +0 -1
  84. package/dist/assets/Events-BVAQ0-5E.js +0 -1
  85. package/dist/assets/Identity-DG2IFYUh.js +0 -1
  86. package/dist/assets/IntegrationDetail-RaObjV5m.js +0 -1
  87. package/dist/assets/Integrations-C-qBCiiD.js +0 -1
  88. package/dist/assets/Network-BqLXlaZV.js +0 -1
  89. package/dist/assets/NodeInferenceDevices-Cm_uziKY.js +0 -1
  90. package/dist/assets/Notifications-ehh4Wa5P.js +0 -1
  91. package/dist/assets/Pipeline-72XZmLkf.js +0 -1
  92. package/dist/assets/Recordings-D-q-9ZgS.js +0 -1
  93. package/dist/assets/ScanNetworkModal-BB5EPjw1.js +0 -1
  94. package/dist/assets/_virtual_mf-localSharedImportMap___mfe_internal__admin_ui_host-CbN7kJoK.js +0 -1
  95. package/dist/assets/circle-check-big-C_hc4h2j.js +0 -1
  96. package/dist/assets/clock-B0tltvJs.js +0 -1
  97. package/dist/assets/dist-tJNbI9ls.js +0 -7
  98. package/dist/assets/hostInit-BivS7viV.js +0 -1
  99. package/dist/assets/index-DVapnh8_.js +0 -4
  100. package/dist/assets/index-DvpMTqDC.css +0 -2
  101. package/dist/assets/plus-BpWlTXb3.js +0 -1
  102. package/dist/assets/remoteEntry-8JprVD_7.js +0 -1
  103. package/dist/assets/rotate-cw-BF1cMOF6.js +0 -1
  104. package/dist/assets/search-B24_Xm4V.js +0 -1
  105. package/dist/assets/square-C8ZyC2yg.js +0 -1
  106. package/dist/assets/src-CSJZQK3p.js +0 -7
@@ -8995,6 +8995,276 @@ var MaskGridDimsSchema = object({
8995
8995
  height: number()
8996
8996
  });
8997
8997
  /**
8998
+ * notification-output — canonical, capability-gated notification delivery.
8999
+ *
9000
+ * Apprise-derived model (see
9001
+ * `docs/superpowers/specs/2026-07-03-notification-output-notifier-matrix.md`):
9002
+ * callers emit ONE canonical `Notification`; each provider declares a
9003
+ * per-kind capability descriptor (`TargetKind`), and the pure degrade
9004
+ * engine (`@camstack/types` `prepareNotification`) transcodes / degrades the
9005
+ * message to what the kind supports — callers never special-case a service.
9006
+ *
9007
+ * DESIGN DECISIONS (locked):
9008
+ * - Target CRUD lives on THIS cap (`upsertTarget` / `deleteTarget` /
9009
+ * `setTargetEnabled`), each provider persisting via the `settings-store`
9010
+ * cap. Rationale: the admin UI needs one uniform surface across the
9011
+ * notifiers addon AND the HA addon; the addon-`globalSettingsSchema`-array
9012
+ * alternative would fork the UI per addon and cannot host the
9013
+ * discovery→adopt flow.
9014
+ * - `listTargetKinds` / `listTargets` / `discoverTargets` return arrays →
9015
+ * the generated cap-mount auto-`concatCollection`-fans them across every
9016
+ * registered provider (notifiers addon + HA addon) so one catalog is
9017
+ * routable. `send` / `testTarget` / CRUD route to ONE provider by the
9018
+ * `addonId` the generated collection router extracts from the call input.
9019
+ * - `Attachment.bytes` is `Uint8Array`. Transport-safe: superjson (the tRPC
9020
+ * transformer) + UDS MsgPack both round-trip typed arrays — already used by
9021
+ * `storage` / `storage-provider` / `recording` caps over the same path. No
9022
+ * base64 fallback needed.
9023
+ *
9024
+ * TODO (deferred, closed-set change — separate decision): add
9025
+ * `providerKind: 'notify'` so notification providers surface on the unified
9026
+ * admin "Integrations" page.
9027
+ */
9028
+ /**
9029
+ * Zentik-derived typed-media enum — the superset across every kind. Each
9030
+ * adapter picks what it supports and the degrade engine filters the rest.
9031
+ */
9032
+ var AttachmentMediaTypeSchema = _enum([
9033
+ "image",
9034
+ "video",
9035
+ "gif",
9036
+ "audio",
9037
+ "icon"
9038
+ ]);
9039
+ /**
9040
+ * A single attachment. Exactly one of `url` (remote source, most adapters
9041
+ * prefer this) or `bytes` (inline source; required for Pushover-style
9042
+ * bytes-only kinds) MUST be present — the degrade engine expresses a
9043
+ * url→bytes fetch as a `needsFetch` directive the adapter executes.
9044
+ */
9045
+ var AttachmentSchema = object({
9046
+ mediaType: AttachmentMediaTypeSchema,
9047
+ url: string().optional(),
9048
+ bytes: _instanceof(Uint8Array).optional(),
9049
+ mime: string().optional(),
9050
+ name: string().optional()
9051
+ }).refine((a) => a.url !== void 0 || a.bytes !== void 0, { message: "Attachment requires either `url` or `bytes`" });
9052
+ var NotificationFormatSchema = _enum([
9053
+ "text",
9054
+ "markdown",
9055
+ "html"
9056
+ ]);
9057
+ /**
9058
+ * The CLOSED icon vocabulary an action button may use.
9059
+ *
9060
+ * A closed set, not a free string, and that is the whole point: an arbitrary
9061
+ * icon name is one that ntfy renders, zentik silently drops, and nobody
9062
+ * notices — the same class of gap as a zone vocabulary nothing produced
9063
+ * ([D35](../../../docs/decisions/adr-0035.md)). Every adapter maps this set or
9064
+ * declares `actionIcons: false` and the degrade engine strips the field.
9065
+ *
9066
+ * Named by INTENT, never by glyph. "check" would tie the vocabulary to one
9067
+ * renderer's icon set; "acknowledge" survives an adapter that draws it
9068
+ * differently.
9069
+ */
9070
+ var NotificationActionIconSchema = _enum([
9071
+ "acknowledge",
9072
+ "dismiss",
9073
+ "silence",
9074
+ "view",
9075
+ "play",
9076
+ "open",
9077
+ "close",
9078
+ "lock",
9079
+ "unlock",
9080
+ "arm",
9081
+ "disarm",
9082
+ "light",
9083
+ "alert"
9084
+ ]);
9085
+ /** A single tap-through action button. */
9086
+ var NotificationActionSchema = object({
9087
+ id: string(),
9088
+ label: string(),
9089
+ url: string().optional(),
9090
+ /** Dropped by the degrade engine for a kind with `caps.actionIcons: false`. */
9091
+ icon: NotificationActionIconSchema.optional(),
9092
+ /**
9093
+ * Renders in a warning style where the notifier supports it.
9094
+ *
9095
+ * A HINT, never a gate. The callback's authority is its token and nothing
9096
+ * else — see `notification-center/action-token.ts` for what that does and
9097
+ * does not buy.
9098
+ */
9099
+ destructive: boolean().optional()
9100
+ });
9101
+ /**
9102
+ * The canonical notification. `body` is the only hard field (Apprise model).
9103
+ * `priority` is a 5-level ORDINAL (1=lowest … 3=normal(default) … 5=urgent),
9104
+ * NOT a fixed severity enum — each kind declares its own `caps.levels` and
9105
+ * the adapter maps this ordinal onto its native level. `level?` is an
9106
+ * optional kind-native level id (`emergency`, `silent`, …) that overrides
9107
+ * `priority` for that one target.
9108
+ */
9109
+ var NotificationSchema = object({
9110
+ body: string(),
9111
+ title: string().optional(),
9112
+ format: NotificationFormatSchema.default("text"),
9113
+ priority: number().int().min(1).max(5).default(3),
9114
+ level: string().optional(),
9115
+ attachments: array(AttachmentSchema).optional(),
9116
+ clickUrl: string().optional(),
9117
+ actions: array(NotificationActionSchema).optional(),
9118
+ sound: string().optional(),
9119
+ ttl: number().optional(),
9120
+ tag: string().optional(),
9121
+ deviceId: number().optional(),
9122
+ eventId: string().optional(),
9123
+ metadata: record(string(), unknown()).optional()
9124
+ });
9125
+ /** One declared native severity/priority level for a kind. */
9126
+ var TargetKindLevelSchema = object({
9127
+ id: string(),
9128
+ label: string(),
9129
+ /** Which canonical priority (1..5) this level maps to. `null` = qualitative-only. */
9130
+ ordinal: number().int().min(1).max(5).nullable(),
9131
+ flags: object({
9132
+ critical: boolean().optional(),
9133
+ silent: boolean().optional(),
9134
+ noPush: boolean().optional()
9135
+ }).optional(),
9136
+ /** e.g. Pushover `emergency` requires `retry` / `expire`. */
9137
+ requires: array(string()).optional(),
9138
+ description: string().optional()
9139
+ });
9140
+ /** The full capability block consulted before dispatch. */
9141
+ var TargetKindCapsSchema = object({
9142
+ attachments: object({
9143
+ mediaTypes: array(AttachmentMediaTypeSchema),
9144
+ mode: _enum([
9145
+ "url",
9146
+ "bytes",
9147
+ "both"
9148
+ ]),
9149
+ max: number().int().nonnegative(),
9150
+ maxBytes: number().int().positive().optional()
9151
+ }),
9152
+ /** Max action buttons (0 = none). */
9153
+ actions: number().int().nonnegative(),
9154
+ /**
9155
+ * Whether this kind renders a per-action ICON.
9156
+ *
9157
+ * `.optional()`, deliberately NOT `.default(false)`: a Zod default does not
9158
+ * run on the addon cap path — three production failures in one day taught
9159
+ * this repo that once. Absent is read as false by the degrade engine, which
9160
+ * is the safe direction: an icon that is not rendered costs nothing, an icon
9161
+ * assumed and dropped costs the operator's trust in the field.
9162
+ */
9163
+ actionIcons: boolean().optional(),
9164
+ levels: array(TargetKindLevelSchema),
9165
+ format: array(NotificationFormatSchema),
9166
+ clickUrl: boolean(),
9167
+ sound: boolean(),
9168
+ ttl: boolean(),
9169
+ bodyMaxLen: number().int().positive()
9170
+ });
9171
+ /**
9172
+ * `configSchema` is a `ConfigUISchema` tree passed through to the admin
9173
+ * FormBuilder. Stored as `z.unknown()` at the cap seam (mirrors
9174
+ * `device-provider.getChildCreationSchema` `CreationSchemaOutputSchema`) —
9175
+ * the union is large and not meant for runtime validation here; the exported
9176
+ * `TargetKind` type re-tightens `configSchema` to `ConfigUISchema`.
9177
+ */
9178
+ var ConfigSchemaPassthrough$1 = unknown();
9179
+ var TargetKindSchema = object({
9180
+ kind: string(),
9181
+ label: string(),
9182
+ icon: string(),
9183
+ /** Stamped by each provider so the concat-fanned catalog stays routable. */
9184
+ addonId: string(),
9185
+ /**
9186
+ * URL of the kind's bundled BRAND icon, served by the providing addon over
9187
+ * its own `addon-routes` surface (`/addon/<addonId>/icons/<kind>`). Absent
9188
+ * when the addon bundles no icon for that kind — the client then falls back
9189
+ * to a neutral glyph rather than rendering the raw `icon` NAME as text.
9190
+ *
9191
+ * Root-relative on purpose: it resolves against whatever origin serves a web
9192
+ * client, and a native client joins it onto its own hub base.
9193
+ *
9194
+ * DECLARED here deliberately. It used to travel as an undeclared passthrough
9195
+ * field that survived only because the runtime cap-router forwards provider
9196
+ * output verbatim — so every consumer had to re-declare it by hand to stop
9197
+ * its own Zod parse from stripping it, and the whole arrangement would have
9198
+ * broken silently the moment output validation was tightened anywhere.
9199
+ */
9200
+ iconUrl: string().optional(),
9201
+ /**
9202
+ * Media type of {@link iconUrl} (`image/svg+xml`, `image/png`, …).
9203
+ *
9204
+ * The server knows this and therefore says it, because the client cannot
9205
+ * safely guess: a React-Native client renders SVG and raster through two
9206
+ * DIFFERENT components (`react-native-svg` vs `expo-image` — expo-image does
9207
+ * not decode SVG on iOS/Android), so without this it silently fell back to a
9208
+ * placeholder glyph for every vector icon while the web build looked fine.
9209
+ *
9210
+ * Absent when {@link iconUrl} is absent, or for a legacy provider that has
9211
+ * not been updated — a client that cannot determine the type should prefer
9212
+ * its raster path, which is the safe default for an unknown image.
9213
+ */
9214
+ iconMediaType: string().optional(),
9215
+ configSchema: ConfigSchemaPassthrough$1,
9216
+ supportsDiscovery: boolean(),
9217
+ caps: TargetKindCapsSchema
9218
+ });
9219
+ /**
9220
+ * A persisted target. `config` holds secrets; providers REDACT secret fields
9221
+ * (return a presence marker only) when serving `listTargets` — never
9222
+ * round-trip a stored secret to the UI.
9223
+ */
9224
+ var TargetSchema = object({
9225
+ id: string(),
9226
+ name: string(),
9227
+ kind: string(),
9228
+ addonId: string(),
9229
+ enabled: boolean(),
9230
+ config: record(string(), unknown())
9231
+ });
9232
+ /** A discovery-surfaced candidate (config is partial + non-secret). */
9233
+ var DiscoveredTargetSchema = object({
9234
+ kind: string(),
9235
+ suggestedName: string(),
9236
+ config: record(string(), unknown())
9237
+ });
9238
+ /** The degrade engine's report — what was resolved / dropped / degraded. */
9239
+ var RenderedAsSchema = object({
9240
+ level: string(),
9241
+ format: NotificationFormatSchema,
9242
+ attachmentsSent: number().int().nonnegative(),
9243
+ actionsSent: number().int().nonnegative(),
9244
+ truncated: boolean(),
9245
+ dropped: array(string())
9246
+ });
9247
+ var SendResultSchema = object({
9248
+ success: boolean(),
9249
+ error: string().optional(),
9250
+ renderedAs: RenderedAsSchema.optional()
9251
+ });
9252
+ /** Same shape as SendResult — kept as a distinct name for the test panel. */
9253
+ var TestResultSchema = SendResultSchema;
9254
+ method(object({}), array(TargetKindSchema)), method(object({}), array(TargetSchema)), method(object({
9255
+ kind: string(),
9256
+ config: record(string(), unknown()).optional()
9257
+ }), array(DiscoveredTargetSchema)), method(object({
9258
+ targetId: string(),
9259
+ notification: NotificationSchema
9260
+ }), SendResultSchema, { kind: "mutation" }), method(object({
9261
+ targetId: string(),
9262
+ sample: NotificationSchema.optional()
9263
+ }), TestResultSchema, { kind: "mutation" }), method(object({ target: TargetSchema }), TargetSchema, { kind: "mutation" }), method(object({ targetId: string() }), _void(), { kind: "mutation" }), method(object({
9264
+ targetId: string(),
9265
+ enabled: boolean()
9266
+ }), _void(), { kind: "mutation" });
9267
+ /**
8998
9268
  * notification-rules — the Notification Center rule surface (P1 core).
8999
9269
  *
9000
9270
  * Spec: `docs/superpowers/specs/2026-07-22-notification-center-requirements.md`
@@ -9164,6 +9434,41 @@ var NcRuleActionSchema = discriminatedUnion("kind", [object({
9164
9434
  args: record(string(), unknown()).optional()
9165
9435
  })]);
9166
9436
  /**
9437
+ * A named, ordered run of steps with its own throttle.
9438
+ *
9439
+ * `minDelaySec` exists because a noisy rule otherwise hammers a physical
9440
+ * actuator — the rule's own cooldown governs NOTIFICATIONS, which is a
9441
+ * different budget from "how often may this gate actually open".
9442
+ */
9443
+ var NcRuleActionSequenceSchema = object({
9444
+ name: string().min(1).max(120),
9445
+ enabled: boolean(),
9446
+ minDelaySec: number().int().min(0).max(86400).optional(),
9447
+ actions: array(NcRuleActionSchema).min(1)
9448
+ });
9449
+ /**
9450
+ * One button carried by the notification, running a named sequence on tap.
9451
+ *
9452
+ * **Read this before adding a button that does something physical.** The tap
9453
+ * arrives over a link that travelled through third-party infrastructure — ntfy,
9454
+ * a push relay, whatever forwarded the message — and the callback's ONLY
9455
+ * authority is the token in that link: single-use, short-lived, bound to this
9456
+ * one action of this one notification. It does not identify who tapped.
9457
+ * Whoever holds the notification can run the button, once, inside the window.
9458
+ * That is the operator's explicit choice (2026-08-05), and `destructive` is a
9459
+ * rendering hint, not a second gate. [D47](decisions/adr-0047.md).
9460
+ */
9461
+ var NcRuleNotificationButtonSchema = object({
9462
+ /** Stable id — travels in the callback and identifies the button in logs. */
9463
+ id: string().min(1).max(64),
9464
+ label: string().min(1).max(40),
9465
+ /** Name of a sequence in `onTrigger`. The dispatcher drops a button whose
9466
+ * sequence does not exist rather than minting a token for nothing. */
9467
+ sequence: string().min(1).max(120),
9468
+ icon: NotificationActionIconSchema.optional(),
9469
+ destructive: boolean().optional()
9470
+ });
9471
+ /**
9167
9472
  * Sequences a rule runs, by hook point.
9168
9473
  *
9169
9474
  * ONLY `onTrigger` is here, deliberately. The reference also has activation /
@@ -9171,14 +9476,26 @@ var NcRuleActionSchema = discriminatedUnion("kind", [object({
9171
9476
  * repo's expensive failure mode is declaring a surface nothing produces, so a
9172
9477
  * hook appears here in the same change that produces its edge, never before.
9173
9478
  */
9174
- var NcRuleActionsSchema = object({
9175
- /** Runs when the rule MATCHES. */
9176
- onTrigger: array(object({
9177
- name: string().min(1).max(120),
9178
- enabled: boolean(),
9179
- minDelaySec: number().int().min(0).max(86400).optional(),
9180
- actions: array(NcRuleActionSchema).min(1)
9181
- })).optional() });
9479
+ var NcRuleActionsSchema = object({
9480
+ /** Runs when the rule MATCHES. */
9481
+ onTrigger: array(NcRuleActionSequenceSchema).optional(),
9482
+ /**
9483
+ * Buttons the NOTIFICATION carries, each running one of this rule's
9484
+ * sequences when tapped.
9485
+ *
9486
+ * Deliberately a REFERENCE to a sequence rather than a second place to
9487
+ * author steps. A button that could define its own actions would be a
9488
+ * parallel actuation vocabulary — the executor's device-scope check, the
9489
+ * stop-at-first-failure rule and the per-sequence throttle all live on
9490
+ * sequences, and a second authoring surface would drift from every one of
9491
+ * them.
9492
+ *
9493
+ * A sequence reachable ONLY by a button simply appears in `onTrigger` with
9494
+ * `enabled: false`: it is then authored, throttled and validated like the
9495
+ * rest, and nothing runs it automatically.
9496
+ */
9497
+ buttons: array(NcRuleNotificationButtonSchema).max(8).optional()
9498
+ });
9182
9499
  var NcConditionsSchema = object({
9183
9500
  /** Gate on ANOTHER device's current state (the alarm armed, a switch on). */
9184
9501
  deviceState: object({
@@ -15121,6 +15438,20 @@ var GetInputSchema = object({ id: string() });
15121
15438
  var AddInputSchema = object({
15122
15439
  kind: string().min(1),
15123
15440
  name: string().min(1),
15441
+ /**
15442
+ * ADOPT an existing id instead of minting a new one.
15443
+ *
15444
+ * Written the day this cost an outage. A broker's id is not a detail: HA
15445
+ * devices carry it inside their `stableId` (`ha:ha_004:dev:…`), so a broker
15446
+ * lost from config and re-added as `ha_001` leaves every one of its devices
15447
+ * bound to a broker that no longer exists. Re-entering the password under the
15448
+ * ORIGINAL id turns a multi-step device migration back into re-entering a
15449
+ * password.
15450
+ *
15451
+ * A provider MUST refuse an id that is already in use — adopting a live
15452
+ * broker's id would silently take it over.
15453
+ */
15454
+ id: string().min(1).optional(),
15124
15455
  /** Kind-specific settings (e.g. MQTT `{url,username,password}` or HA
15125
15456
  * `{baseUrl,accessToken}`). Validated by the kind-specific provider
15126
15457
  * branch on receipt — invalid shape rejects the add. */
@@ -16166,7 +16497,20 @@ method(object({
16166
16497
  }), _void(), {
16167
16498
  kind: "mutation",
16168
16499
  auth: "admin"
16169
- }), method(object({ addonId: string() }), array(SavedDeviceRowSchema)), method(object({ addonId: string().optional() }), array(DeviceInfoSchema)), method(object({ deviceId: number() }), DeviceInfoSchema.nullable()), method(object({ parentDeviceId: number() }), array(DeviceInfoSchema)), method(object({ deviceId: number() }), object({
16500
+ }), method(object({ addonId: string() }), array(SavedDeviceRowSchema)), method(object({
16501
+ addonId: string().optional(),
16502
+ /**
16503
+ * `slim` omits `config` (returned `{}`), `metadata` (null) and the
16504
+ * `sourceInfo` derived from config — and skips the per-device settings
16505
+ * read that produces them. Everything identifying a device (id, name,
16506
+ * type, online, features, isCamera, parent/link ids) is unchanged.
16507
+ * Do not use it for dispatch routing, which needs `sourceInfo`.
16508
+ */
16509
+ projection: _enum(["full", "slim"]).optional(),
16510
+ /** Return only camera devices. Filtering server-side instead of
16511
+ * shipping 293 rows to find 12. */
16512
+ isCamera: boolean().optional()
16513
+ }), array(DeviceInfoSchema)), method(object({ deviceId: number() }), DeviceInfoSchema.nullable()), method(object({ parentDeviceId: number() }), array(DeviceInfoSchema)), method(object({ deviceId: number() }), object({
16170
16514
  mode: DeviceLinkModeSchema,
16171
16515
  devices: array(LinkedDeviceSchema)
16172
16516
  })), method(object({ deviceId: number() }), array(StreamSourceEntrySchema$1)), method(object({ deviceId: number() }), array(ConfigEntrySchema)), method(object({ deviceId: number() }), ConfigUISchemaOutput), method(object({
@@ -16605,14 +16949,14 @@ var LlmProfileSchema = object({
16605
16949
  /** ConfigUISchema tree passed through untyped on the wire (the
16606
16950
  * notification-output `ConfigSchemaPassthrough` precedent at
16607
16951
  * notification-output.cap.ts:151); the exported TS type re-tightens it. */
16608
- var ConfigSchemaPassthrough$1 = unknown();
16952
+ var ConfigSchemaPassthrough = unknown();
16609
16953
  var LlmProfileKindDescriptorSchema = object({
16610
16954
  kind: LlmProfileKindSchema,
16611
16955
  label: string(),
16612
16956
  icon: string(),
16613
16957
  /** Stamped by each provider so the concat-fanned catalog stays routable. */
16614
16958
  addonId: string(),
16615
- configSchema: ConfigSchemaPassthrough$1
16959
+ configSchema: ConfigSchemaPassthrough
16616
16960
  });
16617
16961
  var LlmDefaultSelectorSchema = union([object({ consumer: string() }), object({ purpose: _enum(["text", "vision"]) })]);
16618
16962
  var LlmDefaultSchema = object({
@@ -17143,228 +17487,6 @@ var NetworkEndpointEntrySchema = NetworkEndpointSchema.extend({
17143
17487
  });
17144
17488
  method(_void(), NetworkEndpointSchema, { kind: "mutation" }), method(_void(), _void(), { kind: "mutation" }), method(_void(), NetworkEndpointSchema.nullable()), method(_void(), NetworkAccessStatusSchema), method(_void(), array(NetworkEndpointEntrySchema).readonly());
17145
17489
  /**
17146
- * notification-output — canonical, capability-gated notification delivery.
17147
- *
17148
- * Apprise-derived model (see
17149
- * `docs/superpowers/specs/2026-07-03-notification-output-notifier-matrix.md`):
17150
- * callers emit ONE canonical `Notification`; each provider declares a
17151
- * per-kind capability descriptor (`TargetKind`), and the pure degrade
17152
- * engine (`@camstack/types` `prepareNotification`) transcodes / degrades the
17153
- * message to what the kind supports — callers never special-case a service.
17154
- *
17155
- * DESIGN DECISIONS (locked):
17156
- * - Target CRUD lives on THIS cap (`upsertTarget` / `deleteTarget` /
17157
- * `setTargetEnabled`), each provider persisting via the `settings-store`
17158
- * cap. Rationale: the admin UI needs one uniform surface across the
17159
- * notifiers addon AND the HA addon; the addon-`globalSettingsSchema`-array
17160
- * alternative would fork the UI per addon and cannot host the
17161
- * discovery→adopt flow.
17162
- * - `listTargetKinds` / `listTargets` / `discoverTargets` return arrays →
17163
- * the generated cap-mount auto-`concatCollection`-fans them across every
17164
- * registered provider (notifiers addon + HA addon) so one catalog is
17165
- * routable. `send` / `testTarget` / CRUD route to ONE provider by the
17166
- * `addonId` the generated collection router extracts from the call input.
17167
- * - `Attachment.bytes` is `Uint8Array`. Transport-safe: superjson (the tRPC
17168
- * transformer) + UDS MsgPack both round-trip typed arrays — already used by
17169
- * `storage` / `storage-provider` / `recording` caps over the same path. No
17170
- * base64 fallback needed.
17171
- *
17172
- * TODO (deferred, closed-set change — separate decision): add
17173
- * `providerKind: 'notify'` so notification providers surface on the unified
17174
- * admin "Integrations" page.
17175
- */
17176
- /**
17177
- * Zentik-derived typed-media enum — the superset across every kind. Each
17178
- * adapter picks what it supports and the degrade engine filters the rest.
17179
- */
17180
- var AttachmentMediaTypeSchema = _enum([
17181
- "image",
17182
- "video",
17183
- "gif",
17184
- "audio",
17185
- "icon"
17186
- ]);
17187
- /**
17188
- * A single attachment. Exactly one of `url` (remote source, most adapters
17189
- * prefer this) or `bytes` (inline source; required for Pushover-style
17190
- * bytes-only kinds) MUST be present — the degrade engine expresses a
17191
- * url→bytes fetch as a `needsFetch` directive the adapter executes.
17192
- */
17193
- var AttachmentSchema = object({
17194
- mediaType: AttachmentMediaTypeSchema,
17195
- url: string().optional(),
17196
- bytes: _instanceof(Uint8Array).optional(),
17197
- mime: string().optional(),
17198
- name: string().optional()
17199
- }).refine((a) => a.url !== void 0 || a.bytes !== void 0, { message: "Attachment requires either `url` or `bytes`" });
17200
- var NotificationFormatSchema = _enum([
17201
- "text",
17202
- "markdown",
17203
- "html"
17204
- ]);
17205
- /** A single tap-through action button. */
17206
- var NotificationActionSchema = object({
17207
- id: string(),
17208
- label: string(),
17209
- url: string().optional()
17210
- });
17211
- /**
17212
- * The canonical notification. `body` is the only hard field (Apprise model).
17213
- * `priority` is a 5-level ORDINAL (1=lowest … 3=normal(default) … 5=urgent),
17214
- * NOT a fixed severity enum — each kind declares its own `caps.levels` and
17215
- * the adapter maps this ordinal onto its native level. `level?` is an
17216
- * optional kind-native level id (`emergency`, `silent`, …) that overrides
17217
- * `priority` for that one target.
17218
- */
17219
- var NotificationSchema = object({
17220
- body: string(),
17221
- title: string().optional(),
17222
- format: NotificationFormatSchema.default("text"),
17223
- priority: number().int().min(1).max(5).default(3),
17224
- level: string().optional(),
17225
- attachments: array(AttachmentSchema).optional(),
17226
- clickUrl: string().optional(),
17227
- actions: array(NotificationActionSchema).optional(),
17228
- sound: string().optional(),
17229
- ttl: number().optional(),
17230
- tag: string().optional(),
17231
- deviceId: number().optional(),
17232
- eventId: string().optional(),
17233
- metadata: record(string(), unknown()).optional()
17234
- });
17235
- /** One declared native severity/priority level for a kind. */
17236
- var TargetKindLevelSchema = object({
17237
- id: string(),
17238
- label: string(),
17239
- /** Which canonical priority (1..5) this level maps to. `null` = qualitative-only. */
17240
- ordinal: number().int().min(1).max(5).nullable(),
17241
- flags: object({
17242
- critical: boolean().optional(),
17243
- silent: boolean().optional(),
17244
- noPush: boolean().optional()
17245
- }).optional(),
17246
- /** e.g. Pushover `emergency` requires `retry` / `expire`. */
17247
- requires: array(string()).optional(),
17248
- description: string().optional()
17249
- });
17250
- /** The full capability block consulted before dispatch. */
17251
- var TargetKindCapsSchema = object({
17252
- attachments: object({
17253
- mediaTypes: array(AttachmentMediaTypeSchema),
17254
- mode: _enum([
17255
- "url",
17256
- "bytes",
17257
- "both"
17258
- ]),
17259
- max: number().int().nonnegative(),
17260
- maxBytes: number().int().positive().optional()
17261
- }),
17262
- /** Max action buttons (0 = none). */
17263
- actions: number().int().nonnegative(),
17264
- levels: array(TargetKindLevelSchema),
17265
- format: array(NotificationFormatSchema),
17266
- clickUrl: boolean(),
17267
- sound: boolean(),
17268
- ttl: boolean(),
17269
- bodyMaxLen: number().int().positive()
17270
- });
17271
- /**
17272
- * `configSchema` is a `ConfigUISchema` tree passed through to the admin
17273
- * FormBuilder. Stored as `z.unknown()` at the cap seam (mirrors
17274
- * `device-provider.getChildCreationSchema` `CreationSchemaOutputSchema`) —
17275
- * the union is large and not meant for runtime validation here; the exported
17276
- * `TargetKind` type re-tightens `configSchema` to `ConfigUISchema`.
17277
- */
17278
- var ConfigSchemaPassthrough = unknown();
17279
- var TargetKindSchema = object({
17280
- kind: string(),
17281
- label: string(),
17282
- icon: string(),
17283
- /** Stamped by each provider so the concat-fanned catalog stays routable. */
17284
- addonId: string(),
17285
- /**
17286
- * URL of the kind's bundled BRAND icon, served by the providing addon over
17287
- * its own `addon-routes` surface (`/addon/<addonId>/icons/<kind>`). Absent
17288
- * when the addon bundles no icon for that kind — the client then falls back
17289
- * to a neutral glyph rather than rendering the raw `icon` NAME as text.
17290
- *
17291
- * Root-relative on purpose: it resolves against whatever origin serves a web
17292
- * client, and a native client joins it onto its own hub base.
17293
- *
17294
- * DECLARED here deliberately. It used to travel as an undeclared passthrough
17295
- * field that survived only because the runtime cap-router forwards provider
17296
- * output verbatim — so every consumer had to re-declare it by hand to stop
17297
- * its own Zod parse from stripping it, and the whole arrangement would have
17298
- * broken silently the moment output validation was tightened anywhere.
17299
- */
17300
- iconUrl: string().optional(),
17301
- /**
17302
- * Media type of {@link iconUrl} (`image/svg+xml`, `image/png`, …).
17303
- *
17304
- * The server knows this and therefore says it, because the client cannot
17305
- * safely guess: a React-Native client renders SVG and raster through two
17306
- * DIFFERENT components (`react-native-svg` vs `expo-image` — expo-image does
17307
- * not decode SVG on iOS/Android), so without this it silently fell back to a
17308
- * placeholder glyph for every vector icon while the web build looked fine.
17309
- *
17310
- * Absent when {@link iconUrl} is absent, or for a legacy provider that has
17311
- * not been updated — a client that cannot determine the type should prefer
17312
- * its raster path, which is the safe default for an unknown image.
17313
- */
17314
- iconMediaType: string().optional(),
17315
- configSchema: ConfigSchemaPassthrough,
17316
- supportsDiscovery: boolean(),
17317
- caps: TargetKindCapsSchema
17318
- });
17319
- /**
17320
- * A persisted target. `config` holds secrets; providers REDACT secret fields
17321
- * (return a presence marker only) when serving `listTargets` — never
17322
- * round-trip a stored secret to the UI.
17323
- */
17324
- var TargetSchema = object({
17325
- id: string(),
17326
- name: string(),
17327
- kind: string(),
17328
- addonId: string(),
17329
- enabled: boolean(),
17330
- config: record(string(), unknown())
17331
- });
17332
- /** A discovery-surfaced candidate (config is partial + non-secret). */
17333
- var DiscoveredTargetSchema = object({
17334
- kind: string(),
17335
- suggestedName: string(),
17336
- config: record(string(), unknown())
17337
- });
17338
- /** The degrade engine's report — what was resolved / dropped / degraded. */
17339
- var RenderedAsSchema = object({
17340
- level: string(),
17341
- format: NotificationFormatSchema,
17342
- attachmentsSent: number().int().nonnegative(),
17343
- actionsSent: number().int().nonnegative(),
17344
- truncated: boolean(),
17345
- dropped: array(string())
17346
- });
17347
- var SendResultSchema = object({
17348
- success: boolean(),
17349
- error: string().optional(),
17350
- renderedAs: RenderedAsSchema.optional()
17351
- });
17352
- /** Same shape as SendResult — kept as a distinct name for the test panel. */
17353
- var TestResultSchema = SendResultSchema;
17354
- method(object({}), array(TargetKindSchema)), method(object({}), array(TargetSchema)), method(object({
17355
- kind: string(),
17356
- config: record(string(), unknown()).optional()
17357
- }), array(DiscoveredTargetSchema)), method(object({
17358
- targetId: string(),
17359
- notification: NotificationSchema
17360
- }), SendResultSchema, { kind: "mutation" }), method(object({
17361
- targetId: string(),
17362
- sample: NotificationSchema.optional()
17363
- }), TestResultSchema, { kind: "mutation" }), method(object({ target: TargetSchema }), TargetSchema, { kind: "mutation" }), method(object({ targetId: string() }), _void(), { kind: "mutation" }), method(object({
17364
- targetId: string(),
17365
- enabled: boolean()
17366
- }), _void(), { kind: "mutation" });
17367
- /**
17368
17490
  * core-blocks — user-authored TypeScript, stored in the kernel and executed in
17369
17491
  * its own process.
17370
17492
  *
@@ -17708,6 +17830,11 @@ var EventKindDescriptorSchema = object({
17708
17830
  deviceId: number()
17709
17831
  })
17710
17832
  });
17833
+ /** One camera's event vocabulary, as returned by `listEventKindsBatch`. */
17834
+ var EventKindsForDeviceSchema = object({
17835
+ deviceId: number(),
17836
+ kinds: array(EventKindDescriptorSchema).readonly()
17837
+ });
17711
17838
  var SensorEventSchema = object({
17712
17839
  id: string(),
17713
17840
  /** The CAMERA the event is attributed to (a sensor linked to N cameras
@@ -17964,6 +18091,19 @@ var MediaFileSchema = object({
17964
18091
  sizeBytes: number(),
17965
18092
  timestamp: number()
17966
18093
  });
18094
+ /**
18095
+ * One media row WITHOUT its bytes.
18096
+ *
18097
+ * A track's media is 5-8 MB of base64 (measured: 7.67 MB across 23 files for a
18098
+ * 140 s track), and a client that renders tiles from the media data plane needs
18099
+ * to know only WHAT EXISTS — the bytes then arrive per tile, lazily, over HTTP
18100
+ * with an immutable cache, instead of all at once inside a tRPC response that
18101
+ * blocks the whole view.
18102
+ *
18103
+ * `sizeBytes` is carried because it is what lets a client decide between the
18104
+ * stored blob and a `?variant=thumb` rendering without fetching either.
18105
+ */
18106
+ var MediaFileInfoSchema = MediaFileSchema.omit({ base64: true });
17967
18107
  var DEFAULT_EVENT_QUERY_LIMIT = 1e3;
17968
18108
  var MAX_EVENT_QUERY_LIMIT = 5e3;
17969
18109
  var DeviceEventQueryInput = object({
@@ -18113,7 +18253,7 @@ DeviceType.Camera, method(object({ deviceId: number() }), array(TrackSchema).rea
18113
18253
  }), array(TrackSchema).readonly()), method(RecentTracksQueryInput, RecentTracksPageSchema), method(object({ deviceId: number() }), _void(), {
18114
18254
  kind: "mutation",
18115
18255
  auth: "admin"
18116
- }), method(DeviceEventQueryInput, array(MotionEventSchema).readonly()), method(ObjectEventQueryInput, array(ObjectEventSchema).readonly()), method(DeviceEventQueryInput, array(AudioEventSchema).readonly()), method(object({ deviceId: number() }), array(EventKindDescriptorSchema).readonly()), method(object({
18256
+ }), method(DeviceEventQueryInput, array(MotionEventSchema).readonly()), method(ObjectEventQueryInput, array(ObjectEventSchema).readonly()), method(DeviceEventQueryInput, array(AudioEventSchema).readonly()), method(object({ deviceId: number() }), array(EventKindDescriptorSchema).readonly()), method(object({ deviceIds: array(number()).min(1).max(200) }), array(EventKindsForDeviceSchema).readonly()), method(object({
18117
18257
  deviceId: number(),
18118
18258
  since: number().optional(),
18119
18259
  until: number().optional(),
@@ -18187,7 +18327,7 @@ DeviceType.Camera, method(object({ deviceId: number() }), array(TrackSchema).rea
18187
18327
  }), array(MediaFileSchema).readonly()), method(object({
18188
18328
  trackId: string(),
18189
18329
  kinds: array(MediaFileKindEnum).optional()
18190
- }), array(MediaFileSchema).readonly()), method(SearchObjectEventsInput, array(ScoredObjectEventSchema).readonly()), object({
18330
+ }), array(MediaFileSchema).readonly()), method(object({ trackId: string() }), array(MediaFileInfoSchema).readonly()), method(SearchObjectEventsInput, array(ScoredObjectEventSchema).readonly()), object({
18191
18331
  deviceId: number(),
18192
18332
  timestamp: number(),
18193
18333
  frameWidth: number(),
@@ -24991,6 +25131,12 @@ Object.freeze({
24991
25131
  addonId: null,
24992
25132
  access: "view"
24993
25133
  },
25134
+ "pipelineAnalytics.listEventKindsBatch": {
25135
+ capName: "pipeline-analytics",
25136
+ capScope: "device",
25137
+ addonId: null,
25138
+ access: "view"
25139
+ },
24994
25140
  "pipelineAnalytics.listOpsLog": {
24995
25141
  capName: "pipeline-analytics",
24996
25142
  capScope: "device",
@@ -25003,6 +25149,12 @@ Object.freeze({
25003
25149
  addonId: null,
25004
25150
  access: "view"
25005
25151
  },
25152
+ "pipelineAnalytics.listTrackMedia": {
25153
+ capName: "pipeline-analytics",
25154
+ capScope: "device",
25155
+ addonId: null,
25156
+ access: "view"
25157
+ },
25006
25158
  "pipelineAnalytics.listTracks": {
25007
25159
  capName: "pipeline-analytics",
25008
25160
  capScope: "device",
@@ -27132,7 +27284,7 @@ var AdminUIAddon = class extends BaseAddon {
27132
27284
  capability: adminUiCapability,
27133
27285
  provider: {
27134
27286
  getStaticDir: async () => ({ staticDir: resolveAdminUiDistDir() }),
27135
- getVersion: async () => ({ version: "1.2.27" })
27287
+ getVersion: async () => ({ version: "1.2.29" })
27136
27288
  }
27137
27289
  }];
27138
27290
  }