@camstack/addon-provider-reolink 1.1.12 → 1.1.14

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 (3) hide show
  1. package/dist/addon.js +288 -19
  2. package/dist/addon.mjs +288 -19
  3. package/package.json +1 -1
package/dist/addon.js CHANGED
@@ -4655,7 +4655,7 @@ function _instanceof(cls, params = {}) {
4655
4655
  return inst;
4656
4656
  }
4657
4657
  //#endregion
4658
- //#region ../types/dist/sleep-C2M2zF7x.mjs
4658
+ //#region ../types/dist/sleep-MHm--th-.mjs
4659
4659
  var EventCategory = /* @__PURE__ */ function(EventCategory) {
4660
4660
  EventCategory["SystemBoot"] = "system.boot";
4661
4661
  EventCategory["SystemAddonsReady"] = "system.addons-ready";
@@ -5917,6 +5917,7 @@ var CamStreamKindSchema = _enum([
5917
5917
  "pull-rtsp",
5918
5918
  "pull-rtmp",
5919
5919
  "pull-http",
5920
+ "pull-flv",
5920
5921
  "pull-rfc4571",
5921
5922
  "push-annexb",
5922
5923
  "derived"
@@ -7263,7 +7264,21 @@ var StorageLocationDeclarationSchema = object({
7263
7264
  * slots (e.g. `recordingsLow` → `recordings`) so operators only need to
7264
7265
  * configure the primary location.
7265
7266
  */
7266
- defaultsTo: string().optional()
7267
+ defaultsTo: string().optional(),
7268
+ /**
7269
+ * Which node root the seeded `<id>:default` instance is placed under on a
7270
+ * FRESH install:
7271
+ * - `'data'` (default) — the node's data dir (`CAMSTACK_DATA` / boot dir),
7272
+ * the appData volume. Right for small/durable data (backups, logs, models).
7273
+ * - `'media'` — the dedicated media volume (`CAMSTACK_MEDIA_ROOT`) when that
7274
+ * env is set, else falls back to the data root. Right for bulky, hot media
7275
+ * (recordings, event media) that should stay off the appData disk.
7276
+ *
7277
+ * Only affects the seeded default's `basePath`; operators can repoint any
7278
+ * location afterwards, and a `defaultsTo` slot inherits its parent's root
7279
+ * regardless of this field. Absent (the common case) is treated as `'data'`.
7280
+ */
7281
+ defaultRoot: _enum(["data", "media"]).optional()
7267
7282
  });
7268
7283
  var DecoderStatsSchema = object({
7269
7284
  inputFps: number(),
@@ -8658,6 +8673,10 @@ var RtspRestreamEntrySchema = object({
8658
8673
  var BrokerRtspClientSchema = object({
8659
8674
  sessionId: string(),
8660
8675
  remoteAddr: string(),
8676
+ /** Client `User-Agent` (e.g. `recorder` for the recorder's RTSP pull), or
8677
+ * null/absent when the client sent none. Lets the UI label a consumer by
8678
+ * purpose. Optional so a client built against an older schema stays valid. */
8679
+ userAgent: string().nullish(),
8661
8680
  playing: boolean(),
8662
8681
  muted: boolean(),
8663
8682
  connectedAt: number(),
@@ -16273,7 +16292,7 @@ var AddBrokerInputSchema = object({
16273
16292
  });
16274
16293
  var AddBrokerResultSchema = object({ id: string() });
16275
16294
  var IdInputSchema = object({ id: string() });
16276
- var TestResultSchema = discriminatedUnion("ok", [object({
16295
+ var TestResultSchema$1 = discriminatedUnion("ok", [object({
16277
16296
  ok: literal(true),
16278
16297
  latencyMs: number()
16279
16298
  }), object({
@@ -16296,7 +16315,7 @@ var StatusSchema = object({
16296
16315
  brokerCount: number(),
16297
16316
  embeddedRunning: boolean()
16298
16317
  });
16299
- method(_void(), array(BrokerInfoSchema)), method(IdInputSchema, BrokerConnectionDetailsSchema), method(AddBrokerInputSchema, AddBrokerResultSchema, { kind: "mutation" }), method(IdInputSchema, _void(), { kind: "mutation" }), method(IdInputSchema, TestResultSchema, { kind: "mutation" }), method(StartEmbeddedInputSchema, StartEmbeddedResultSchema, { kind: "mutation" }), method(IdInputSchema, _void(), { kind: "mutation" }), method(_void(), StatusSchema);
16318
+ method(_void(), array(BrokerInfoSchema)), method(IdInputSchema, BrokerConnectionDetailsSchema), method(AddBrokerInputSchema, AddBrokerResultSchema, { kind: "mutation" }), method(IdInputSchema, _void(), { kind: "mutation" }), method(IdInputSchema, TestResultSchema$1, { kind: "mutation" }), method(StartEmbeddedInputSchema, StartEmbeddedResultSchema, { kind: "mutation" }), method(IdInputSchema, _void(), { kind: "mutation" }), method(_void(), StatusSchema);
16300
16319
  var NetworkEndpointSchema = object({
16301
16320
  url: string(),
16302
16321
  hostname: string(),
@@ -16330,23 +16349,198 @@ var NetworkEndpointEntrySchema = NetworkEndpointSchema.extend({
16330
16349
  sourcePort: number().optional()
16331
16350
  });
16332
16351
  method(_void(), NetworkEndpointSchema, { kind: "mutation" }), method(_void(), _void(), { kind: "mutation" }), method(_void(), NetworkEndpointSchema.nullable()), method(_void(), NetworkAccessStatusSchema), method(_void(), array(NetworkEndpointEntrySchema).readonly());
16333
- method(object({
16334
- title: string(),
16352
+ /**
16353
+ * notification-output — canonical, capability-gated notification delivery.
16354
+ *
16355
+ * Apprise-derived model (see
16356
+ * `docs/superpowers/specs/2026-07-03-notification-output-notifier-matrix.md`):
16357
+ * callers emit ONE canonical `Notification`; each provider declares a
16358
+ * per-kind capability descriptor (`TargetKind`), and the pure degrade
16359
+ * engine (`@camstack/types` `prepareNotification`) transcodes / degrades the
16360
+ * message to what the kind supports — callers never special-case a service.
16361
+ *
16362
+ * DESIGN DECISIONS (locked):
16363
+ * - Target CRUD lives on THIS cap (`upsertTarget` / `deleteTarget` /
16364
+ * `setTargetEnabled`), each provider persisting via the `settings-store`
16365
+ * cap. Rationale: the admin UI needs one uniform surface across the
16366
+ * notifiers addon AND the HA addon; the addon-`globalSettingsSchema`-array
16367
+ * alternative would fork the UI per addon and cannot host the
16368
+ * discovery→adopt flow.
16369
+ * - `listTargetKinds` / `listTargets` / `discoverTargets` return arrays →
16370
+ * the generated cap-mount auto-`concatCollection`-fans them across every
16371
+ * registered provider (notifiers addon + HA addon) so one catalog is
16372
+ * routable. `send` / `testTarget` / CRUD route to ONE provider by the
16373
+ * `addonId` the generated collection router extracts from the call input.
16374
+ * - `Attachment.bytes` is `Uint8Array`. Transport-safe: superjson (the tRPC
16375
+ * transformer) + UDS MsgPack both round-trip typed arrays — already used by
16376
+ * `storage` / `storage-provider` / `recording` caps over the same path. No
16377
+ * base64 fallback needed.
16378
+ *
16379
+ * TODO (deferred, closed-set change — separate decision): add
16380
+ * `providerKind: 'notify'` so notification providers surface on the unified
16381
+ * admin "Integrations" page.
16382
+ */
16383
+ /**
16384
+ * Zentik-derived typed-media enum — the superset across every kind. Each
16385
+ * adapter picks what it supports and the degrade engine filters the rest.
16386
+ */
16387
+ var AttachmentMediaTypeSchema = _enum([
16388
+ "image",
16389
+ "video",
16390
+ "gif",
16391
+ "audio",
16392
+ "icon"
16393
+ ]);
16394
+ /**
16395
+ * A single attachment. Exactly one of `url` (remote source, most adapters
16396
+ * prefer this) or `bytes` (inline source; required for Pushover-style
16397
+ * bytes-only kinds) MUST be present — the degrade engine expresses a
16398
+ * url→bytes fetch as a `needsFetch` directive the adapter executes.
16399
+ */
16400
+ var AttachmentSchema = object({
16401
+ mediaType: AttachmentMediaTypeSchema,
16402
+ url: string().optional(),
16403
+ bytes: _instanceof(Uint8Array).optional(),
16404
+ mime: string().optional(),
16405
+ name: string().optional()
16406
+ }).refine((a) => a.url !== void 0 || a.bytes !== void 0, { message: "Attachment requires either `url` or `bytes`" });
16407
+ var NotificationFormatSchema = _enum([
16408
+ "text",
16409
+ "markdown",
16410
+ "html"
16411
+ ]);
16412
+ /** A single tap-through action button. */
16413
+ var NotificationActionSchema = object({
16414
+ id: string(),
16415
+ label: string(),
16416
+ url: string().optional()
16417
+ });
16418
+ /**
16419
+ * The canonical notification. `body` is the only hard field (Apprise model).
16420
+ * `priority` is a 5-level ORDINAL (1=lowest … 3=normal(default) … 5=urgent),
16421
+ * NOT a fixed severity enum — each kind declares its own `caps.levels` and
16422
+ * the adapter maps this ordinal onto its native level. `level?` is an
16423
+ * optional kind-native level id (`emergency`, `silent`, …) that overrides
16424
+ * `priority` for that one target.
16425
+ */
16426
+ var NotificationSchema = object({
16335
16427
  body: string(),
16336
- imageUrl: string().optional(),
16428
+ title: string().optional(),
16429
+ format: NotificationFormatSchema.default("text"),
16430
+ priority: number().int().min(1).max(5).default(3),
16431
+ level: string().optional(),
16432
+ attachments: array(AttachmentSchema).optional(),
16433
+ clickUrl: string().optional(),
16434
+ actions: array(NotificationActionSchema).optional(),
16435
+ sound: string().optional(),
16436
+ ttl: number().optional(),
16437
+ tag: string().optional(),
16337
16438
  deviceId: number().optional(),
16338
16439
  eventId: string().optional(),
16339
- priority: _enum([
16340
- "low",
16341
- "normal",
16342
- "high",
16343
- "critical"
16344
- ]).default("normal"),
16345
16440
  metadata: record(string(), unknown()).optional()
16346
- }), _void(), { kind: "mutation" }), method(_void(), object({
16441
+ });
16442
+ /** One declared native severity/priority level for a kind. */
16443
+ var TargetKindLevelSchema = object({
16444
+ id: string(),
16445
+ label: string(),
16446
+ /** Which canonical priority (1..5) this level maps to. `null` = qualitative-only. */
16447
+ ordinal: number().int().min(1).max(5).nullable(),
16448
+ flags: object({
16449
+ critical: boolean().optional(),
16450
+ silent: boolean().optional(),
16451
+ noPush: boolean().optional()
16452
+ }).optional(),
16453
+ /** e.g. Pushover `emergency` requires `retry` / `expire`. */
16454
+ requires: array(string()).optional(),
16455
+ description: string().optional()
16456
+ });
16457
+ /** The full capability block consulted before dispatch. */
16458
+ var TargetKindCapsSchema = object({
16459
+ attachments: object({
16460
+ mediaTypes: array(AttachmentMediaTypeSchema),
16461
+ mode: _enum([
16462
+ "url",
16463
+ "bytes",
16464
+ "both"
16465
+ ]),
16466
+ max: number().int().nonnegative(),
16467
+ maxBytes: number().int().positive().optional()
16468
+ }),
16469
+ /** Max action buttons (0 = none). */
16470
+ actions: number().int().nonnegative(),
16471
+ levels: array(TargetKindLevelSchema),
16472
+ format: array(NotificationFormatSchema),
16473
+ clickUrl: boolean(),
16474
+ sound: boolean(),
16475
+ ttl: boolean(),
16476
+ bodyMaxLen: number().int().positive()
16477
+ });
16478
+ /**
16479
+ * `configSchema` is a `ConfigUISchema` tree passed through to the admin
16480
+ * FormBuilder. Stored as `z.unknown()` at the cap seam (mirrors
16481
+ * `device-provider.getChildCreationSchema` `CreationSchemaOutputSchema`) —
16482
+ * the union is large and not meant for runtime validation here; the exported
16483
+ * `TargetKind` type re-tightens `configSchema` to `ConfigUISchema`.
16484
+ */
16485
+ var ConfigSchemaPassthrough = unknown();
16486
+ var TargetKindSchema = object({
16487
+ kind: string(),
16488
+ label: string(),
16489
+ icon: string(),
16490
+ /** Stamped by each provider so the concat-fanned catalog stays routable. */
16491
+ addonId: string(),
16492
+ configSchema: ConfigSchemaPassthrough,
16493
+ supportsDiscovery: boolean(),
16494
+ caps: TargetKindCapsSchema
16495
+ });
16496
+ /**
16497
+ * A persisted target. `config` holds secrets; providers REDACT secret fields
16498
+ * (return a presence marker only) when serving `listTargets` — never
16499
+ * round-trip a stored secret to the UI.
16500
+ */
16501
+ var TargetSchema = object({
16502
+ id: string(),
16503
+ name: string(),
16504
+ kind: string(),
16505
+ addonId: string(),
16506
+ enabled: boolean(),
16507
+ config: record(string(), unknown())
16508
+ });
16509
+ /** A discovery-surfaced candidate (config is partial + non-secret). */
16510
+ var DiscoveredTargetSchema = object({
16511
+ kind: string(),
16512
+ suggestedName: string(),
16513
+ config: record(string(), unknown())
16514
+ });
16515
+ /** The degrade engine's report — what was resolved / dropped / degraded. */
16516
+ var RenderedAsSchema = object({
16517
+ level: string(),
16518
+ format: NotificationFormatSchema,
16519
+ attachmentsSent: number().int().nonnegative(),
16520
+ actionsSent: number().int().nonnegative(),
16521
+ truncated: boolean(),
16522
+ dropped: array(string())
16523
+ });
16524
+ var SendResultSchema = object({
16347
16525
  success: boolean(),
16348
- error: string().optional()
16349
- }), { kind: "mutation" });
16526
+ error: string().optional(),
16527
+ renderedAs: RenderedAsSchema.optional()
16528
+ });
16529
+ /** Same shape as SendResult — kept as a distinct name for the test panel. */
16530
+ var TestResultSchema = SendResultSchema;
16531
+ method(object({}), array(TargetKindSchema)), method(object({}), array(TargetSchema)), method(object({
16532
+ kind: string(),
16533
+ config: record(string(), unknown()).optional()
16534
+ }), array(DiscoveredTargetSchema)), method(object({
16535
+ targetId: string(),
16536
+ notification: NotificationSchema
16537
+ }), SendResultSchema, { kind: "mutation" }), method(object({
16538
+ targetId: string(),
16539
+ sample: NotificationSchema.optional()
16540
+ }), TestResultSchema, { kind: "mutation" }), method(object({ target: TargetSchema }), TargetSchema, { kind: "mutation" }), method(object({ targetId: string() }), _void(), { kind: "mutation" }), method(object({
16541
+ targetId: string(),
16542
+ enabled: boolean()
16543
+ }), _void(), { kind: "mutation" });
16350
16544
  /**
16351
16545
  * Zod schemas for persisted record types.
16352
16546
  *
@@ -22713,13 +22907,49 @@ Object.freeze({
22713
22907
  addonId: null,
22714
22908
  access: "create"
22715
22909
  },
22910
+ "notificationOutput.deleteTarget": {
22911
+ capName: "notification-output",
22912
+ capScope: "system",
22913
+ addonId: null,
22914
+ access: "delete"
22915
+ },
22916
+ "notificationOutput.discoverTargets": {
22917
+ capName: "notification-output",
22918
+ capScope: "system",
22919
+ addonId: null,
22920
+ access: "view"
22921
+ },
22922
+ "notificationOutput.listTargetKinds": {
22923
+ capName: "notification-output",
22924
+ capScope: "system",
22925
+ addonId: null,
22926
+ access: "view"
22927
+ },
22928
+ "notificationOutput.listTargets": {
22929
+ capName: "notification-output",
22930
+ capScope: "system",
22931
+ addonId: null,
22932
+ access: "view"
22933
+ },
22716
22934
  "notificationOutput.send": {
22717
22935
  capName: "notification-output",
22718
22936
  capScope: "system",
22719
22937
  addonId: null,
22720
22938
  access: "create"
22721
22939
  },
22722
- "notificationOutput.sendTest": {
22940
+ "notificationOutput.setTargetEnabled": {
22941
+ capName: "notification-output",
22942
+ capScope: "system",
22943
+ addonId: null,
22944
+ access: "create"
22945
+ },
22946
+ "notificationOutput.testTarget": {
22947
+ capName: "notification-output",
22948
+ capScope: "system",
22949
+ addonId: null,
22950
+ access: "create"
22951
+ },
22952
+ "notificationOutput.upsertTarget": {
22723
22953
  capName: "notification-output",
22724
22954
  capScope: "system",
22725
22955
  addonId: null,
@@ -215218,7 +215448,7 @@ function isNativeObjectForwardingEnabled(capState) {
215218
215448
  }
215219
215449
  //#endregion
215220
215450
  //#region src/stream-routing.ts
215221
- var KIND_RE = /^(native|rtsp|rtmp):(.+)$/;
215451
+ var KIND_RE = /^(native|rtsp|rtmp|flv):(.+)$/;
215222
215452
  var CH_PROFILE_RE = /^ch(\d+)-(main|sub|ext)$/;
215223
215453
  var PROFILE_ONLY_RE = /^(main|sub|ext)$/;
215224
215454
  /**
@@ -215268,6 +215498,7 @@ function kindLabel(kind) {
215268
215498
  case "native": return "Native";
215269
215499
  case "rtsp": return "RTSP";
215270
215500
  case "rtmp": return "RTMP";
215501
+ case "flv": return "FLV";
215271
215502
  }
215272
215503
  }
215273
215504
  /**
@@ -218714,7 +218945,7 @@ var ReolinkCamera = class ReolinkCamera extends BaseDevice {
218714
218945
  */
218715
218946
  resolveAudioCodecApi() {
218716
218947
  const router = this.ctx.api.audioCodec;
218717
- if (!router) throw new Error("Reolink intercom: `audio-codec` capability is not mounted. Install + enable the `addon-audio-codec-nodeav` addon (or any other addon that provides the `audio-codec` capability) before opening a talk session.");
218948
+ if (!router) throw new Error("Reolink intercom: `audio-codec` capability is not mounted. Install + enable the `addon-audio-codec-ffmpeg` addon (or any other addon that provides the `audio-codec` capability) before opening a talk session.");
218718
218949
  return {
218719
218950
  createDecodeSession: (input) => router.createDecodeSession.mutate(input),
218720
218951
  pushEncodedFrame: (input) => router.pushEncodedFrame.mutate(input),
@@ -219154,6 +219385,23 @@ var ReolinkCamera = class ReolinkCamera extends BaseDevice {
219154
219385
  }
219155
219386
  }
219156
219387
  /**
219388
+ * Synthesize the HTTP-FLV pull URL for a (channel, profile) pair. FLV is
219389
+ * served off the Reolink Baichuan media port (default 1935, app `bcs`) as
219390
+ * `channel<channel>_<profile>.bcs` — the same shape Frigate/go2rtc use. The
219391
+ * lib does NOT surface this URL, so we build it from the SAME connection
219392
+ * creds the RTSP/native paths use (`host`/`username`/`password` on the
219393
+ * device config), URL-encoding the credentials.
219394
+ *
219395
+ * Returns `null` when the host isn't locally resolvable (hub-child cameras
219396
+ * borrow the parent's socket and hold no own `host`), so the caller skips
219397
+ * FLV publication for those devices rather than emitting a broken URL.
219398
+ */
219399
+ buildFlvUrl(channel, profile) {
219400
+ const host = this.config.get("host");
219401
+ if (typeof host !== "string" || host.length === 0) return null;
219402
+ return `http://${host}/flv?port=1935&app=bcs&stream=channel${channel}_${profile}.bcs&user=${encodeURIComponent(String(this.config.get("username") ?? ""))}&password=${encodeURIComponent(String(this.config.get("password") ?? ""))}`;
219403
+ }
219404
+ /**
219157
219405
  * Heavy path: talk to the camera and assemble the descriptors. Only ever
219158
219406
  * entered when there is no cached catalog (see `buildStreamCatalog`).
219159
219407
  *
@@ -219239,6 +219487,27 @@ var ReolinkCamera = class ReolinkCamera extends BaseDevice {
219239
219487
  };
219240
219488
  enqueueAlt("rtsp", streamOptions.rtspStreams);
219241
219489
  enqueueAlt("rtmp", streamOptions.rtmpStreams);
219490
+ for (const s of streamOptions.nativeStreams) {
219491
+ const channel = s.channel ?? (isChild ? ownChannel : 0);
219492
+ const id = buildCamStreamId("flv", channel, s.profile, channelCount);
219493
+ if (desired.has(id)) continue;
219494
+ const flvUrl = this.buildFlvUrl(channel, s.profile);
219495
+ if (!flvUrl) break;
219496
+ const m = s.metadata;
219497
+ desired.set(id, {
219498
+ camStreamId: id,
219499
+ kind: "pull-flv",
219500
+ label: streamLabel(s, "flv"),
219501
+ url: flvUrl,
219502
+ autoEligible: false,
219503
+ ...m && m.width > 0 && m.height > 0 ? { resolution: {
219504
+ width: m.width,
219505
+ height: m.height
219506
+ } } : {},
219507
+ ...m && m.frameRate > 0 ? { fps: m.frameRate } : {},
219508
+ ...normalizeCodecName(m?.videoEncType) ? { codec: normalizeCodecName(m?.videoEncType) } : {}
219509
+ });
219510
+ }
219242
219511
  if (desired.size === 0) {
219243
219512
  this.ctx.logger.warn("buildStreamCatalog: camera reports no streams — empty catalog", { tags: { deviceId: this.id } });
219244
219513
  return [];
package/dist/addon.mjs CHANGED
@@ -4650,7 +4650,7 @@ function _instanceof(cls, params = {}) {
4650
4650
  return inst;
4651
4651
  }
4652
4652
  //#endregion
4653
- //#region ../types/dist/sleep-C2M2zF7x.mjs
4653
+ //#region ../types/dist/sleep-MHm--th-.mjs
4654
4654
  var EventCategory = /* @__PURE__ */ function(EventCategory) {
4655
4655
  EventCategory["SystemBoot"] = "system.boot";
4656
4656
  EventCategory["SystemAddonsReady"] = "system.addons-ready";
@@ -5912,6 +5912,7 @@ var CamStreamKindSchema = _enum([
5912
5912
  "pull-rtsp",
5913
5913
  "pull-rtmp",
5914
5914
  "pull-http",
5915
+ "pull-flv",
5915
5916
  "pull-rfc4571",
5916
5917
  "push-annexb",
5917
5918
  "derived"
@@ -7258,7 +7259,21 @@ var StorageLocationDeclarationSchema = object({
7258
7259
  * slots (e.g. `recordingsLow` → `recordings`) so operators only need to
7259
7260
  * configure the primary location.
7260
7261
  */
7261
- defaultsTo: string().optional()
7262
+ defaultsTo: string().optional(),
7263
+ /**
7264
+ * Which node root the seeded `<id>:default` instance is placed under on a
7265
+ * FRESH install:
7266
+ * - `'data'` (default) — the node's data dir (`CAMSTACK_DATA` / boot dir),
7267
+ * the appData volume. Right for small/durable data (backups, logs, models).
7268
+ * - `'media'` — the dedicated media volume (`CAMSTACK_MEDIA_ROOT`) when that
7269
+ * env is set, else falls back to the data root. Right for bulky, hot media
7270
+ * (recordings, event media) that should stay off the appData disk.
7271
+ *
7272
+ * Only affects the seeded default's `basePath`; operators can repoint any
7273
+ * location afterwards, and a `defaultsTo` slot inherits its parent's root
7274
+ * regardless of this field. Absent (the common case) is treated as `'data'`.
7275
+ */
7276
+ defaultRoot: _enum(["data", "media"]).optional()
7262
7277
  });
7263
7278
  var DecoderStatsSchema = object({
7264
7279
  inputFps: number(),
@@ -8653,6 +8668,10 @@ var RtspRestreamEntrySchema = object({
8653
8668
  var BrokerRtspClientSchema = object({
8654
8669
  sessionId: string(),
8655
8670
  remoteAddr: string(),
8671
+ /** Client `User-Agent` (e.g. `recorder` for the recorder's RTSP pull), or
8672
+ * null/absent when the client sent none. Lets the UI label a consumer by
8673
+ * purpose. Optional so a client built against an older schema stays valid. */
8674
+ userAgent: string().nullish(),
8656
8675
  playing: boolean(),
8657
8676
  muted: boolean(),
8658
8677
  connectedAt: number(),
@@ -16268,7 +16287,7 @@ var AddBrokerInputSchema = object({
16268
16287
  });
16269
16288
  var AddBrokerResultSchema = object({ id: string() });
16270
16289
  var IdInputSchema = object({ id: string() });
16271
- var TestResultSchema = discriminatedUnion("ok", [object({
16290
+ var TestResultSchema$1 = discriminatedUnion("ok", [object({
16272
16291
  ok: literal(true),
16273
16292
  latencyMs: number()
16274
16293
  }), object({
@@ -16291,7 +16310,7 @@ var StatusSchema = object({
16291
16310
  brokerCount: number(),
16292
16311
  embeddedRunning: boolean()
16293
16312
  });
16294
- method(_void(), array(BrokerInfoSchema)), method(IdInputSchema, BrokerConnectionDetailsSchema), method(AddBrokerInputSchema, AddBrokerResultSchema, { kind: "mutation" }), method(IdInputSchema, _void(), { kind: "mutation" }), method(IdInputSchema, TestResultSchema, { kind: "mutation" }), method(StartEmbeddedInputSchema, StartEmbeddedResultSchema, { kind: "mutation" }), method(IdInputSchema, _void(), { kind: "mutation" }), method(_void(), StatusSchema);
16313
+ method(_void(), array(BrokerInfoSchema)), method(IdInputSchema, BrokerConnectionDetailsSchema), method(AddBrokerInputSchema, AddBrokerResultSchema, { kind: "mutation" }), method(IdInputSchema, _void(), { kind: "mutation" }), method(IdInputSchema, TestResultSchema$1, { kind: "mutation" }), method(StartEmbeddedInputSchema, StartEmbeddedResultSchema, { kind: "mutation" }), method(IdInputSchema, _void(), { kind: "mutation" }), method(_void(), StatusSchema);
16295
16314
  var NetworkEndpointSchema = object({
16296
16315
  url: string(),
16297
16316
  hostname: string(),
@@ -16325,23 +16344,198 @@ var NetworkEndpointEntrySchema = NetworkEndpointSchema.extend({
16325
16344
  sourcePort: number().optional()
16326
16345
  });
16327
16346
  method(_void(), NetworkEndpointSchema, { kind: "mutation" }), method(_void(), _void(), { kind: "mutation" }), method(_void(), NetworkEndpointSchema.nullable()), method(_void(), NetworkAccessStatusSchema), method(_void(), array(NetworkEndpointEntrySchema).readonly());
16328
- method(object({
16329
- title: string(),
16347
+ /**
16348
+ * notification-output — canonical, capability-gated notification delivery.
16349
+ *
16350
+ * Apprise-derived model (see
16351
+ * `docs/superpowers/specs/2026-07-03-notification-output-notifier-matrix.md`):
16352
+ * callers emit ONE canonical `Notification`; each provider declares a
16353
+ * per-kind capability descriptor (`TargetKind`), and the pure degrade
16354
+ * engine (`@camstack/types` `prepareNotification`) transcodes / degrades the
16355
+ * message to what the kind supports — callers never special-case a service.
16356
+ *
16357
+ * DESIGN DECISIONS (locked):
16358
+ * - Target CRUD lives on THIS cap (`upsertTarget` / `deleteTarget` /
16359
+ * `setTargetEnabled`), each provider persisting via the `settings-store`
16360
+ * cap. Rationale: the admin UI needs one uniform surface across the
16361
+ * notifiers addon AND the HA addon; the addon-`globalSettingsSchema`-array
16362
+ * alternative would fork the UI per addon and cannot host the
16363
+ * discovery→adopt flow.
16364
+ * - `listTargetKinds` / `listTargets` / `discoverTargets` return arrays →
16365
+ * the generated cap-mount auto-`concatCollection`-fans them across every
16366
+ * registered provider (notifiers addon + HA addon) so one catalog is
16367
+ * routable. `send` / `testTarget` / CRUD route to ONE provider by the
16368
+ * `addonId` the generated collection router extracts from the call input.
16369
+ * - `Attachment.bytes` is `Uint8Array`. Transport-safe: superjson (the tRPC
16370
+ * transformer) + UDS MsgPack both round-trip typed arrays — already used by
16371
+ * `storage` / `storage-provider` / `recording` caps over the same path. No
16372
+ * base64 fallback needed.
16373
+ *
16374
+ * TODO (deferred, closed-set change — separate decision): add
16375
+ * `providerKind: 'notify'` so notification providers surface on the unified
16376
+ * admin "Integrations" page.
16377
+ */
16378
+ /**
16379
+ * Zentik-derived typed-media enum — the superset across every kind. Each
16380
+ * adapter picks what it supports and the degrade engine filters the rest.
16381
+ */
16382
+ var AttachmentMediaTypeSchema = _enum([
16383
+ "image",
16384
+ "video",
16385
+ "gif",
16386
+ "audio",
16387
+ "icon"
16388
+ ]);
16389
+ /**
16390
+ * A single attachment. Exactly one of `url` (remote source, most adapters
16391
+ * prefer this) or `bytes` (inline source; required for Pushover-style
16392
+ * bytes-only kinds) MUST be present — the degrade engine expresses a
16393
+ * url→bytes fetch as a `needsFetch` directive the adapter executes.
16394
+ */
16395
+ var AttachmentSchema = object({
16396
+ mediaType: AttachmentMediaTypeSchema,
16397
+ url: string().optional(),
16398
+ bytes: _instanceof(Uint8Array).optional(),
16399
+ mime: string().optional(),
16400
+ name: string().optional()
16401
+ }).refine((a) => a.url !== void 0 || a.bytes !== void 0, { message: "Attachment requires either `url` or `bytes`" });
16402
+ var NotificationFormatSchema = _enum([
16403
+ "text",
16404
+ "markdown",
16405
+ "html"
16406
+ ]);
16407
+ /** A single tap-through action button. */
16408
+ var NotificationActionSchema = object({
16409
+ id: string(),
16410
+ label: string(),
16411
+ url: string().optional()
16412
+ });
16413
+ /**
16414
+ * The canonical notification. `body` is the only hard field (Apprise model).
16415
+ * `priority` is a 5-level ORDINAL (1=lowest … 3=normal(default) … 5=urgent),
16416
+ * NOT a fixed severity enum — each kind declares its own `caps.levels` and
16417
+ * the adapter maps this ordinal onto its native level. `level?` is an
16418
+ * optional kind-native level id (`emergency`, `silent`, …) that overrides
16419
+ * `priority` for that one target.
16420
+ */
16421
+ var NotificationSchema = object({
16330
16422
  body: string(),
16331
- imageUrl: string().optional(),
16423
+ title: string().optional(),
16424
+ format: NotificationFormatSchema.default("text"),
16425
+ priority: number().int().min(1).max(5).default(3),
16426
+ level: string().optional(),
16427
+ attachments: array(AttachmentSchema).optional(),
16428
+ clickUrl: string().optional(),
16429
+ actions: array(NotificationActionSchema).optional(),
16430
+ sound: string().optional(),
16431
+ ttl: number().optional(),
16432
+ tag: string().optional(),
16332
16433
  deviceId: number().optional(),
16333
16434
  eventId: string().optional(),
16334
- priority: _enum([
16335
- "low",
16336
- "normal",
16337
- "high",
16338
- "critical"
16339
- ]).default("normal"),
16340
16435
  metadata: record(string(), unknown()).optional()
16341
- }), _void(), { kind: "mutation" }), method(_void(), object({
16436
+ });
16437
+ /** One declared native severity/priority level for a kind. */
16438
+ var TargetKindLevelSchema = object({
16439
+ id: string(),
16440
+ label: string(),
16441
+ /** Which canonical priority (1..5) this level maps to. `null` = qualitative-only. */
16442
+ ordinal: number().int().min(1).max(5).nullable(),
16443
+ flags: object({
16444
+ critical: boolean().optional(),
16445
+ silent: boolean().optional(),
16446
+ noPush: boolean().optional()
16447
+ }).optional(),
16448
+ /** e.g. Pushover `emergency` requires `retry` / `expire`. */
16449
+ requires: array(string()).optional(),
16450
+ description: string().optional()
16451
+ });
16452
+ /** The full capability block consulted before dispatch. */
16453
+ var TargetKindCapsSchema = object({
16454
+ attachments: object({
16455
+ mediaTypes: array(AttachmentMediaTypeSchema),
16456
+ mode: _enum([
16457
+ "url",
16458
+ "bytes",
16459
+ "both"
16460
+ ]),
16461
+ max: number().int().nonnegative(),
16462
+ maxBytes: number().int().positive().optional()
16463
+ }),
16464
+ /** Max action buttons (0 = none). */
16465
+ actions: number().int().nonnegative(),
16466
+ levels: array(TargetKindLevelSchema),
16467
+ format: array(NotificationFormatSchema),
16468
+ clickUrl: boolean(),
16469
+ sound: boolean(),
16470
+ ttl: boolean(),
16471
+ bodyMaxLen: number().int().positive()
16472
+ });
16473
+ /**
16474
+ * `configSchema` is a `ConfigUISchema` tree passed through to the admin
16475
+ * FormBuilder. Stored as `z.unknown()` at the cap seam (mirrors
16476
+ * `device-provider.getChildCreationSchema` `CreationSchemaOutputSchema`) —
16477
+ * the union is large and not meant for runtime validation here; the exported
16478
+ * `TargetKind` type re-tightens `configSchema` to `ConfigUISchema`.
16479
+ */
16480
+ var ConfigSchemaPassthrough = unknown();
16481
+ var TargetKindSchema = object({
16482
+ kind: string(),
16483
+ label: string(),
16484
+ icon: string(),
16485
+ /** Stamped by each provider so the concat-fanned catalog stays routable. */
16486
+ addonId: string(),
16487
+ configSchema: ConfigSchemaPassthrough,
16488
+ supportsDiscovery: boolean(),
16489
+ caps: TargetKindCapsSchema
16490
+ });
16491
+ /**
16492
+ * A persisted target. `config` holds secrets; providers REDACT secret fields
16493
+ * (return a presence marker only) when serving `listTargets` — never
16494
+ * round-trip a stored secret to the UI.
16495
+ */
16496
+ var TargetSchema = object({
16497
+ id: string(),
16498
+ name: string(),
16499
+ kind: string(),
16500
+ addonId: string(),
16501
+ enabled: boolean(),
16502
+ config: record(string(), unknown())
16503
+ });
16504
+ /** A discovery-surfaced candidate (config is partial + non-secret). */
16505
+ var DiscoveredTargetSchema = object({
16506
+ kind: string(),
16507
+ suggestedName: string(),
16508
+ config: record(string(), unknown())
16509
+ });
16510
+ /** The degrade engine's report — what was resolved / dropped / degraded. */
16511
+ var RenderedAsSchema = object({
16512
+ level: string(),
16513
+ format: NotificationFormatSchema,
16514
+ attachmentsSent: number().int().nonnegative(),
16515
+ actionsSent: number().int().nonnegative(),
16516
+ truncated: boolean(),
16517
+ dropped: array(string())
16518
+ });
16519
+ var SendResultSchema = object({
16342
16520
  success: boolean(),
16343
- error: string().optional()
16344
- }), { kind: "mutation" });
16521
+ error: string().optional(),
16522
+ renderedAs: RenderedAsSchema.optional()
16523
+ });
16524
+ /** Same shape as SendResult — kept as a distinct name for the test panel. */
16525
+ var TestResultSchema = SendResultSchema;
16526
+ method(object({}), array(TargetKindSchema)), method(object({}), array(TargetSchema)), method(object({
16527
+ kind: string(),
16528
+ config: record(string(), unknown()).optional()
16529
+ }), array(DiscoveredTargetSchema)), method(object({
16530
+ targetId: string(),
16531
+ notification: NotificationSchema
16532
+ }), SendResultSchema, { kind: "mutation" }), method(object({
16533
+ targetId: string(),
16534
+ sample: NotificationSchema.optional()
16535
+ }), TestResultSchema, { kind: "mutation" }), method(object({ target: TargetSchema }), TargetSchema, { kind: "mutation" }), method(object({ targetId: string() }), _void(), { kind: "mutation" }), method(object({
16536
+ targetId: string(),
16537
+ enabled: boolean()
16538
+ }), _void(), { kind: "mutation" });
16345
16539
  /**
16346
16540
  * Zod schemas for persisted record types.
16347
16541
  *
@@ -22708,13 +22902,49 @@ Object.freeze({
22708
22902
  addonId: null,
22709
22903
  access: "create"
22710
22904
  },
22905
+ "notificationOutput.deleteTarget": {
22906
+ capName: "notification-output",
22907
+ capScope: "system",
22908
+ addonId: null,
22909
+ access: "delete"
22910
+ },
22911
+ "notificationOutput.discoverTargets": {
22912
+ capName: "notification-output",
22913
+ capScope: "system",
22914
+ addonId: null,
22915
+ access: "view"
22916
+ },
22917
+ "notificationOutput.listTargetKinds": {
22918
+ capName: "notification-output",
22919
+ capScope: "system",
22920
+ addonId: null,
22921
+ access: "view"
22922
+ },
22923
+ "notificationOutput.listTargets": {
22924
+ capName: "notification-output",
22925
+ capScope: "system",
22926
+ addonId: null,
22927
+ access: "view"
22928
+ },
22711
22929
  "notificationOutput.send": {
22712
22930
  capName: "notification-output",
22713
22931
  capScope: "system",
22714
22932
  addonId: null,
22715
22933
  access: "create"
22716
22934
  },
22717
- "notificationOutput.sendTest": {
22935
+ "notificationOutput.setTargetEnabled": {
22936
+ capName: "notification-output",
22937
+ capScope: "system",
22938
+ addonId: null,
22939
+ access: "create"
22940
+ },
22941
+ "notificationOutput.testTarget": {
22942
+ capName: "notification-output",
22943
+ capScope: "system",
22944
+ addonId: null,
22945
+ access: "create"
22946
+ },
22947
+ "notificationOutput.upsertTarget": {
22718
22948
  capName: "notification-output",
22719
22949
  capScope: "system",
22720
22950
  addonId: null,
@@ -215198,7 +215428,7 @@ function isNativeObjectForwardingEnabled(capState) {
215198
215428
  }
215199
215429
  //#endregion
215200
215430
  //#region src/stream-routing.ts
215201
- var KIND_RE = /^(native|rtsp|rtmp):(.+)$/;
215431
+ var KIND_RE = /^(native|rtsp|rtmp|flv):(.+)$/;
215202
215432
  var CH_PROFILE_RE = /^ch(\d+)-(main|sub|ext)$/;
215203
215433
  var PROFILE_ONLY_RE = /^(main|sub|ext)$/;
215204
215434
  /**
@@ -215248,6 +215478,7 @@ function kindLabel(kind) {
215248
215478
  case "native": return "Native";
215249
215479
  case "rtsp": return "RTSP";
215250
215480
  case "rtmp": return "RTMP";
215481
+ case "flv": return "FLV";
215251
215482
  }
215252
215483
  }
215253
215484
  /**
@@ -218694,7 +218925,7 @@ var ReolinkCamera = class ReolinkCamera extends BaseDevice {
218694
218925
  */
218695
218926
  resolveAudioCodecApi() {
218696
218927
  const router = this.ctx.api.audioCodec;
218697
- if (!router) throw new Error("Reolink intercom: `audio-codec` capability is not mounted. Install + enable the `addon-audio-codec-nodeav` addon (or any other addon that provides the `audio-codec` capability) before opening a talk session.");
218928
+ if (!router) throw new Error("Reolink intercom: `audio-codec` capability is not mounted. Install + enable the `addon-audio-codec-ffmpeg` addon (or any other addon that provides the `audio-codec` capability) before opening a talk session.");
218698
218929
  return {
218699
218930
  createDecodeSession: (input) => router.createDecodeSession.mutate(input),
218700
218931
  pushEncodedFrame: (input) => router.pushEncodedFrame.mutate(input),
@@ -219134,6 +219365,23 @@ var ReolinkCamera = class ReolinkCamera extends BaseDevice {
219134
219365
  }
219135
219366
  }
219136
219367
  /**
219368
+ * Synthesize the HTTP-FLV pull URL for a (channel, profile) pair. FLV is
219369
+ * served off the Reolink Baichuan media port (default 1935, app `bcs`) as
219370
+ * `channel<channel>_<profile>.bcs` — the same shape Frigate/go2rtc use. The
219371
+ * lib does NOT surface this URL, so we build it from the SAME connection
219372
+ * creds the RTSP/native paths use (`host`/`username`/`password` on the
219373
+ * device config), URL-encoding the credentials.
219374
+ *
219375
+ * Returns `null` when the host isn't locally resolvable (hub-child cameras
219376
+ * borrow the parent's socket and hold no own `host`), so the caller skips
219377
+ * FLV publication for those devices rather than emitting a broken URL.
219378
+ */
219379
+ buildFlvUrl(channel, profile) {
219380
+ const host = this.config.get("host");
219381
+ if (typeof host !== "string" || host.length === 0) return null;
219382
+ return `http://${host}/flv?port=1935&app=bcs&stream=channel${channel}_${profile}.bcs&user=${encodeURIComponent(String(this.config.get("username") ?? ""))}&password=${encodeURIComponent(String(this.config.get("password") ?? ""))}`;
219383
+ }
219384
+ /**
219137
219385
  * Heavy path: talk to the camera and assemble the descriptors. Only ever
219138
219386
  * entered when there is no cached catalog (see `buildStreamCatalog`).
219139
219387
  *
@@ -219219,6 +219467,27 @@ var ReolinkCamera = class ReolinkCamera extends BaseDevice {
219219
219467
  };
219220
219468
  enqueueAlt("rtsp", streamOptions.rtspStreams);
219221
219469
  enqueueAlt("rtmp", streamOptions.rtmpStreams);
219470
+ for (const s of streamOptions.nativeStreams) {
219471
+ const channel = s.channel ?? (isChild ? ownChannel : 0);
219472
+ const id = buildCamStreamId("flv", channel, s.profile, channelCount);
219473
+ if (desired.has(id)) continue;
219474
+ const flvUrl = this.buildFlvUrl(channel, s.profile);
219475
+ if (!flvUrl) break;
219476
+ const m = s.metadata;
219477
+ desired.set(id, {
219478
+ camStreamId: id,
219479
+ kind: "pull-flv",
219480
+ label: streamLabel(s, "flv"),
219481
+ url: flvUrl,
219482
+ autoEligible: false,
219483
+ ...m && m.width > 0 && m.height > 0 ? { resolution: {
219484
+ width: m.width,
219485
+ height: m.height
219486
+ } } : {},
219487
+ ...m && m.frameRate > 0 ? { fps: m.frameRate } : {},
219488
+ ...normalizeCodecName(m?.videoEncType) ? { codec: normalizeCodecName(m?.videoEncType) } : {}
219489
+ });
219490
+ }
219222
219491
  if (desired.size === 0) {
219223
219492
  this.ctx.logger.warn("buildStreamCatalog: camera reports no streams — empty catalog", { tags: { deviceId: this.id } });
219224
219493
  return [];
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@camstack/addon-provider-reolink",
3
- "version": "1.1.12",
3
+ "version": "1.1.14",
4
4
  "description": "Reolink camera device provider addon for CamStack — native Baichuan protocol",
5
5
  "keywords": [
6
6
  "camstack",