@camstack/system 1.2.231 → 1.2.233

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 (62) hide show
  1. package/dist/addon-runner.js +1 -1
  2. package/dist/addon-runner.mjs +1 -1
  3. package/dist/addon-utils.d.ts +1 -0
  4. package/dist/addon-utils.js +6 -1
  5. package/dist/addon-utils.mjs +3 -2
  6. package/dist/builtins/addon-pages-aggregator/addon-pages-aggregator.addon.js +1 -1
  7. package/dist/builtins/addon-pages-aggregator/addon-pages-aggregator.addon.mjs +1 -1
  8. package/dist/builtins/addon-widgets-aggregator/addon-widgets-aggregator.addon.js +1 -1
  9. package/dist/builtins/addon-widgets-aggregator/addon-widgets-aggregator.addon.mjs +1 -1
  10. package/dist/builtins/alerts/alerts.addon.js +1 -1
  11. package/dist/builtins/alerts/alerts.addon.mjs +1 -1
  12. package/dist/builtins/backup-orchestrator/backup-orchestrator.addon.js +17 -1
  13. package/dist/builtins/backup-orchestrator/backup-orchestrator.addon.mjs +17 -1
  14. package/dist/builtins/console-logging/index.js +1 -1
  15. package/dist/builtins/console-logging/index.mjs +1 -1
  16. package/dist/builtins/core-blocks/core-blocks.addon.js +1 -1
  17. package/dist/builtins/core-blocks/core-blocks.addon.mjs +1 -1
  18. package/dist/builtins/device-manager/device-config-contribution.d.ts +1 -0
  19. package/dist/builtins/device-manager/device-manager.addon.js +46 -3
  20. package/dist/builtins/device-manager/device-manager.addon.mjs +46 -3
  21. package/dist/builtins/device-manager/native-object-detection-config-schema.d.ts +3 -0
  22. package/dist/builtins/doorbell/virtual-doorbell.addon.js +1 -1
  23. package/dist/builtins/doorbell/virtual-doorbell.addon.mjs +1 -1
  24. package/dist/builtins/hub-forwarder/index.js +1 -1
  25. package/dist/builtins/hub-forwarder/index.mjs +1 -1
  26. package/dist/builtins/liveness-monitor/liveness-monitor.addon.js +1 -1
  27. package/dist/builtins/liveness-monitor/liveness-monitor.addon.mjs +1 -1
  28. package/dist/builtins/local-auth/local-auth.addon.js +1 -1
  29. package/dist/builtins/local-auth/local-auth.addon.mjs +1 -1
  30. package/dist/builtins/local-network/local-network.addon.js +1 -1
  31. package/dist/builtins/local-network/local-network.addon.mjs +1 -1
  32. package/dist/builtins/loki-logging/index.js +1 -1
  33. package/dist/builtins/loki-logging/index.mjs +1 -1
  34. package/dist/builtins/native-metrics/native-metrics.addon.js +1 -1
  35. package/dist/builtins/native-metrics/native-metrics.addon.mjs +1 -1
  36. package/dist/builtins/platform-probe/index.js +1 -1
  37. package/dist/builtins/platform-probe/index.mjs +1 -1
  38. package/dist/builtins/remote-access-orchestrator/remote-access-orchestrator.addon.js +1 -1
  39. package/dist/builtins/remote-access-orchestrator/remote-access-orchestrator.addon.mjs +1 -1
  40. package/dist/builtins/snapshot/index.js +5 -140
  41. package/dist/builtins/snapshot/index.mjs +2 -137
  42. package/dist/builtins/sqlite-storage/filesystem-storage.addon.js +1 -1
  43. package/dist/builtins/sqlite-storage/filesystem-storage.addon.mjs +1 -1
  44. package/dist/builtins/sqlite-storage/sqlite-settings.addon.js +0 -0
  45. package/dist/builtins/sqlite-storage/sqlite-settings.addon.mjs +0 -0
  46. package/dist/builtins/storage-orchestrator/storage-orchestrator.addon.js +1 -1
  47. package/dist/builtins/storage-orchestrator/storage-orchestrator.addon.mjs +1 -1
  48. package/dist/builtins/system-config/system-config.addon.js +1 -1
  49. package/dist/builtins/system-config/system-config.addon.mjs +1 -1
  50. package/dist/builtins/winston-logging/index.js +1 -1
  51. package/dist/builtins/winston-logging/index.mjs +1 -1
  52. package/dist/{custom-action-registry-CM03Xda0.mjs → custom-action-registry-CFHYDHAu.mjs} +1 -1
  53. package/dist/{custom-action-registry-DAFhWd0h.js → custom-action-registry-CWFK5UDj.js} +1 -1
  54. package/dist/{dist-Zhe9cTnK.mjs → dist-BIvXRexm.mjs} +291 -18
  55. package/dist/{dist-DUSfUAgz.js → dist-D3lJV9M7.js} +291 -18
  56. package/dist/index.js +2 -2
  57. package/dist/index.mjs +2 -2
  58. package/dist/{retired-settings-keys-BK_geSOq.mjs → retired-settings-keys-CV3ZLGnJ.mjs} +1 -1
  59. package/dist/{retired-settings-keys-D61PxN8L.js → retired-settings-keys-YPsd2Exa.js} +1 -1
  60. package/dist/snapshot-courtesy-LoJNgHHZ.mjs +140 -0
  61. package/dist/snapshot-courtesy-oeTWCXPv.js +165 -0
  62. package/package.json +1 -1
@@ -1,5 +1,5 @@
1
1
  let zod = require("zod");
2
- //#region ../types/dist/event-category-ZyX6jcse.mjs
2
+ //#region ../types/dist/event-category-BVDXG4tB.mjs
3
3
  var EventCategory = /* @__PURE__ */ function(EventCategory) {
4
4
  EventCategory["SystemBoot"] = "system.boot";
5
5
  EventCategory["SystemAddonsReady"] = "system.addons-ready";
@@ -616,6 +616,20 @@ var EventCategory = /* @__PURE__ */ function(EventCategory) {
616
616
  * pull-reconcile from the provider's `getStatus` on reconnect. */
617
617
  EventCategory["MeshNetworkChanged"] = "network.mesh.changed";
618
618
  EventCategory["BackupCompleted"] = "backup.completed";
619
+ /**
620
+ * A whole backup RUN finished — every destination attempted, win or lose.
621
+ *
622
+ * `backup.completed` fires once per DESTINATION, which is the right grain for
623
+ * a progress UI and the wrong one for a notification: an operator with three
624
+ * destinations would be told three times. And a run where two of three
625
+ * destinations succeeded is not a clean success — a single "backup
626
+ * completed" that hid the failed one would be a lie, so the count of each is
627
+ * carried here and the message says both.
628
+ *
629
+ * Emitted by the backup orchestrator only after the destination loop, so a
630
+ * run that dies during the BUILD phase produces no completion at all.
631
+ */
632
+ EventCategory["BackupRunCompleted"] = "backup.run-completed";
619
633
  EventCategory["BackupRestored"] = "backup.restored";
620
634
  EventCategory["NotificationDispatched"] = "notification.dispatched";
621
635
  EventCategory["NotificationFailed"] = "notification.failed";
@@ -676,7 +690,7 @@ var EventCategory = /* @__PURE__ */ function(EventCategory) {
676
690
  return EventCategory;
677
691
  }({});
678
692
  //#endregion
679
- //#region ../types/dist/sleep-DBKu2-U5.mjs
693
+ //#region ../types/dist/sleep-BEyvfshj.mjs
680
694
  /**
681
695
  * The audio chunk plane's byte format, and the ONE expansion from a coded
682
696
  * window to float samples (D455).
@@ -8486,6 +8500,72 @@ var cameraPipelineConfigCapability = {
8486
8500
  exposesDeviceSettings: true,
8487
8501
  methods: {}
8488
8502
  };
8503
+ /**
8504
+ * The signals a device can emit to WAKE its own stream.
8505
+ *
8506
+ * A camera whose stream is built on demand sleeps until something asks for it,
8507
+ * and "something" cannot be a consumer that is merely attached — a Frigate-style
8508
+ * puller holds a session open for ever, and treating that as demand would keep
8509
+ * a battery camera awake for ever, which is the whole thing the battery is for
8510
+ * (D173). So the wake has to come from the CAMERA: an event it noticed by
8511
+ * itself, with no stream running.
8512
+ *
8513
+ * ## The vocabulary is the PROVIDER'S, not ours
8514
+ *
8515
+ * Like `consumables`, this cap declares no vocabulary of its own. A provider
8516
+ * names each signal with a `code` it chooses and a `label` an operator reads.
8517
+ * Reolink offers motion and camera-native detection; another provider may offer
8518
+ * a tamper, a doorbell press, a PIR, or something no camera in this fleet has
8519
+ * yet. A fixed enum here would mean every new signal is a framework release.
8520
+ *
8521
+ * It is deliberately NOT derived from the caps a device already binds. Whether
8522
+ * a camera CAN push firmware motion is expressed by `motionSources` containing
8523
+ * `'onboard'`, and whether it does AI on-camera by the `native-object-detection`
8524
+ * binding — but both answer "what drives the detection pipeline", which is a
8525
+ * different question from "what may wake a sleeping stream". A camera can do
8526
+ * the first and not be trusted with the second, and the operator picks per
8527
+ * camera. Two questions, two authorities.
8528
+ *
8529
+ * ## Availability is not permission
8530
+ *
8531
+ * `listSignals` says what the device CAN emit. Whether a given signal actually
8532
+ * wakes the stream is the operator's per-camera choice, held by the broker
8533
+ * alongside the cooldown — see the stream-broker cap's wake settings. A
8534
+ * provider declaring a signal is not a provider enabling it.
8535
+ */
8536
+ /** One signal a device can emit. */
8537
+ var StreamSignalSchema = zod.z.object({
8538
+ /** Stable id chosen by the provider, e.g. `'motion'`, `'person'`, `'tamper'`. */
8539
+ code: zod.z.string().min(1),
8540
+ /** What an operator reads in the picker. The provider's own wording. */
8541
+ label: zod.z.string().min(1),
8542
+ /**
8543
+ * Whether the provider recommends this signal ON when a camera is first set
8544
+ * up. A provider knows which of its signals are cheap and reliable; an
8545
+ * operator should not have to discover that by trial. Reolink recommends
8546
+ * both of its own.
8547
+ */
8548
+ recommended: zod.z.boolean()
8549
+ });
8550
+ var StreamSignalsStatusSchema = zod.z.object({
8551
+ signals: zod.z.array(StreamSignalSchema),
8552
+ lastFetchedAt: zod.z.number()
8553
+ });
8554
+ var streamSignalsCapability = {
8555
+ name: "stream-signals",
8556
+ scope: "device",
8557
+ deviceNative: true,
8558
+ mode: "singleton",
8559
+ deviceTypes: Object.values(DeviceType),
8560
+ runtimeState: StreamSignalsStatusSchema,
8561
+ methods: {
8562
+ /**
8563
+ * What this device can emit. Empty is a valid and common answer — most
8564
+ * cameras have nothing to offer here, and an empty list is what makes the
8565
+ * broker's picker show nothing rather than a false choice.
8566
+ */
8567
+ listSignals: method(zod.z.void(), zod.z.array(StreamSignalSchema).readonly()) }
8568
+ };
8489
8569
  /** Stream delivery format. (Relocated from the retired `streaming-engine` cap.) */
8490
8570
  var StreamFormatSchema = zod.z.enum([
8491
8571
  "webrtc",
@@ -8895,6 +8975,46 @@ var streamBrokerCapability = {
8895
8975
  mode: "singleton",
8896
8976
  exposesDeviceSettings: true,
8897
8977
  methods: {
8978
+ /**
8979
+ * A device reporting that one of ITS OWN signals just fired.
8980
+ *
8981
+ * The provider calls this; nothing polls. The broker then decides whether
8982
+ * to wake the source, and the decision has two terms that must BOTH hold:
8983
+ *
8984
+ * - the signal is ENABLED for this camera (the operator's per-camera
8985
+ * choice — a camera may declare a signal the operator does not want
8986
+ * waking anything), and
8987
+ * - a consumer is actually attached and waiting on the prestream. Waking
8988
+ * a camera nobody is watching is the one thing a battery camera must
8989
+ * never do, and a signal arriving with no consumer is exactly that.
8990
+ *
8991
+ * Reported, never inferred: a signal that fires while the stream is
8992
+ * already up refreshes the hold instead of starting a second one, which is
8993
+ * what makes continuous motion keep a camera awake without stacking
8994
+ * timers.
8995
+ *
8996
+ * `code` is the provider's own, from `streamSignals.listSignals`. An
8997
+ * unknown code is REFUSED rather than treated as a wildcard wake — a
8998
+ * provider and an operator disagreeing about a name must not become a
8999
+ * camera that wakes for everything.
9000
+ */
9001
+ reportStreamSignal: method(zod.z.object({
9002
+ deviceId: zod.z.number().int().nonnegative(),
9003
+ /** The provider's signal code. */
9004
+ code: zod.z.string().min(1),
9005
+ /** Ms epoch. Absent ⇒ now. */
9006
+ at: zod.z.number().optional()
9007
+ }), zod.z.object({
9008
+ /** Whether the broker acted on it, and if not, why. */
9009
+ accepted: zod.z.boolean(),
9010
+ reason: zod.z.enum([
9011
+ "woke",
9012
+ "hold-extended",
9013
+ "not-enabled",
9014
+ "no-consumer",
9015
+ "unknown-code"
9016
+ ])
9017
+ }), { kind: "mutation" }),
8898
9018
  publishCameraStream: method(zod.z.object({
8899
9019
  deviceId: zod.z.number().int().nonnegative(),
8900
9020
  camStreamId: zod.z.string().min(1),
@@ -9372,6 +9492,16 @@ var PickStreamRequirementsSchema = zod.z.object({
9372
9492
  acceptCodecs: zod.z.array(StreamCodecSchema).readonly().optional(),
9373
9493
  /** Minimum vertical resolution. Streams shorter than this are dropped. */
9374
9494
  minHeight: zod.z.number().int().positive().optional(),
9495
+ /**
9496
+ * Maximum vertical resolution. Streams TALLER than this are dropped.
9497
+ *
9498
+ * A consumer can have a ceiling as real as its floor: Alexa documents
9499
+ * 480p to 1080p, and a 4K stream is as unusable to an Echo as a 360p one.
9500
+ * Without this the ceiling had to be re-implemented by every caller — and
9501
+ * one caller implementing it privately is how a second scoring authority
9502
+ * gets born.
9503
+ */
9504
+ maxHeight: zod.z.number().int().positive().optional(),
9375
9505
  /** Minimum horizontal resolution. */
9376
9506
  minWidth: zod.z.number().int().positive().optional(),
9377
9507
  /**
@@ -9388,7 +9518,22 @@ var PickStreamRequirementsSchema = zod.z.object({
9388
9518
  * transcoded" guard: if the device is already serving the consumer's
9389
9519
  * codec end-to-end, there's nothing to optimise.
9390
9520
  */
9391
- requireSiblingCodec: zod.z.array(StreamCodecSchema).readonly().optional()
9521
+ requireSiblingCodec: zod.z.array(StreamCodecSchema).readonly().optional(),
9522
+ /**
9523
+ * Whether a stream the consumer CANNOT decode may still be picked, on the
9524
+ * understanding that it will be transcoded.
9525
+ *
9526
+ * Default `false` — today's behaviour, and the right one for a bypass
9527
+ * question ("is there a stream I can forward untouched?"). Set `true` to
9528
+ * ask the larger question: "what is the best stream for me, transcoding if
9529
+ * I must?" A stream that satisfies `acceptCodecs` always outranks one that
9530
+ * does not, so a passthrough is never lost to a transcode; the answer says
9531
+ * which it is in {@link PickedCamStreamSchema.transcodes}.
9532
+ *
9533
+ * This is what lets one picker serve both the bypass and the full source
9534
+ * choice, instead of a consumer scoring privately when the bypass misses.
9535
+ */
9536
+ allowTranscode: zod.z.boolean().optional()
9392
9537
  }).readonly();
9393
9538
  var PickStreamPreferencesSchema = zod.z.object({
9394
9539
  /**
@@ -9402,12 +9547,32 @@ var PickStreamPreferencesSchema = zod.z.object({
9402
9547
  * picks the tallest stream; `'lowest'` picks the shortest (used by
9403
9548
  * memory-constrained consumers / Apple Home guest sessions).
9404
9549
  */
9405
- resolutionPreference: zod.z.enum(["highest", "lowest"]).optional()
9550
+ resolutionPreference: zod.z.enum(["highest", "lowest"]).optional(),
9551
+ /**
9552
+ * The height the consumer actually wants to DELIVER.
9553
+ *
9554
+ * Not a constraint — an ordering. With it set, the SMALLEST stream at or
9555
+ * above the target wins, and only if nothing reaches it does the tallest
9556
+ * take over. Pulling 1296 lines to draw 720 on a 1280x800 Echo panel costs
9557
+ * a decode and buys nothing, and `resolutionPreference: 'highest'` cannot
9558
+ * express that: it says "as big as possible", which is a different wish.
9559
+ *
9560
+ * Ignored when absent, so every existing caller keeps its ordering.
9561
+ */
9562
+ targetHeight: zod.z.number().int().positive().optional()
9406
9563
  }).readonly();
9407
9564
  var PickedCamStreamSchema = zod.z.object({
9408
9565
  camStreamId: zod.z.string(),
9409
9566
  codec: zod.z.string().optional(),
9410
9567
  resolution: CamStreamResolutionSchema.optional(),
9568
+ /**
9569
+ * Whether serving this stream requires a decode + re-encode.
9570
+ *
9571
+ * `false` is a stream the consumer can take as it stands. Only ever `true`
9572
+ * when the caller asked for it with `allowTranscode`, so a caller that did
9573
+ * not ask cannot be handed a cost it never agreed to pay.
9574
+ */
9575
+ transcodes: zod.z.boolean(),
9411
9576
  /** One-line explanation of why this stream won — for logs / debug UI. */
9412
9577
  reason: zod.z.string()
9413
9578
  });
@@ -15484,6 +15649,8 @@ var NcSystemEventKindSchema = zod.z.enum([
15484
15649
  "device-enabled",
15485
15650
  "device-battery-low",
15486
15651
  "device-battery-normal",
15652
+ "device-consumable-low",
15653
+ "device-consumable-normal",
15487
15654
  "stream-online",
15488
15655
  "stream-offline",
15489
15656
  "node-online",
@@ -15502,6 +15669,7 @@ var NcSystemEventKindSchema = zod.z.enum([
15502
15669
  "addon-updated",
15503
15670
  "server-updated",
15504
15671
  "export-completed",
15672
+ "backup-completed",
15505
15673
  "camera-online",
15506
15674
  "camera-offline",
15507
15675
  "camera-disabled",
@@ -24943,10 +25111,28 @@ var webrtcSessionCapability = {
24943
25111
  getSessionState: method(zod.z.object({
24944
25112
  deviceId: zod.z.number().int().nonnegative(),
24945
25113
  sessionId: zod.z.string()
24946
- }), zod.z.object({ pendingRenegotiation: zod.z.object({
24947
- target: WebrtcStreamTargetSchema,
24948
- epoch: zod.z.number()
24949
- }).nullable() }))
25114
+ }), zod.z.object({
25115
+ pendingRenegotiation: zod.z.object({
25116
+ target: WebrtcStreamTargetSchema,
25117
+ epoch: zod.z.number()
25118
+ }).nullable(),
25119
+ /**
25120
+ * Whether the session still EXISTS.
25121
+ *
25122
+ * A consumer that holds a resource for the life of a session needs to
25123
+ * be able to ask, because a session does not always end the way it
25124
+ * began. Measured on this hub 2026-09-13: an Echo that got stuck never
25125
+ * sent `SessionDisconnected`, so the Alexa exporter's
25126
+ * `releaseEgressTranscode` never ran, and a 2304x1296 HEVC to 720p
25127
+ * H.264 transcode kept running for NOBODY for more than twenty
25128
+ * minutes. The WebRTC session had logged `WebRTC session closed`
25129
+ * minutes earlier — the broker knew; the holder had no way to ask.
25130
+ *
25131
+ * `false` for a session id the provider has never heard of, which is
25132
+ * the same answer as "it ended": either way nothing is holding it up.
25133
+ */
25134
+ alive: zod.z.boolean()
25135
+ }))
24950
25136
  },
24951
25137
  /** BIG PLAN 2: declarative mount hint — read by `@camstack/system` `buildCapRouters`. */
24952
25138
  mount: { kind: "server-provided" }
@@ -29995,11 +30181,39 @@ var NativeObjectDetectionStatusSchema = zod.z.object({
29995
30181
  supportedClasses: zod.z.array(NativeObjectClassEnum).readonly(),
29996
30182
  /**
29997
30183
  * Whether forwarding of onboard AI detections is enabled for this device.
29998
- * Default FALSE (opt-in, cold-start) — onboard AI pushes are noisy/sparse and
29999
- * churn the tracker, so forwarding stays off until the operator enables it.
30184
+ *
30185
+ * The COLD-START default is the provider's, and it is not one value for
30186
+ * every camera: a mains camera carries the boxed side-channel and its
30187
+ * detections are usable subjects, so it defaults ON; a battery camera never
30188
+ * attaches that channel, so the same toggle would buy it nothing but a write
30189
+ * — it defaults OFF and an operator who wants it turns it on knowing what it
30190
+ * costs. {@link NativeObjectDetectionOptionsSchema.geometry} is how the UI
30191
+ * says which of the two a camera is.
30000
30192
  */
30001
30193
  enabled: zod.z.boolean()
30002
30194
  });
30195
+ /**
30196
+ * What a camera's onboard detections actually CARRY.
30197
+ *
30198
+ * `boxed` — the firmware ships a bounding box with the class, so a detection is
30199
+ * a subject the pipeline can track, crop and score.
30200
+ * `flag-only` — the firmware ships the class and nothing else. The pipeline
30201
+ * needs geometry to make a subject out of it, so such a push reaches the
30202
+ * tracker as motion and no further. On Reolink this is every battery camera:
30203
+ * the boxes ride the BcMedia sub-stream, which a battery camera never attaches.
30204
+ *
30205
+ * Reported and not inferred, because the operator's question in front of the
30206
+ * toggle is "what do I get", and "a class with no box" and "a subject" are
30207
+ * different answers (D14: the derived form is only as honest as `getOptions`).
30208
+ */
30209
+ var NativeObjectGeometryEnum = zod.z.enum(["boxed", "flag-only"]);
30210
+ var NativeObjectDetectionOptionsSchema = zod.z.object({
30211
+ /** Classes this firmware can detect — the same list the status reports. */
30212
+ supportedClasses: zod.z.array(NativeObjectClassEnum).readonly(),
30213
+ /** What a detection from this camera carries. */
30214
+ geometry: NativeObjectGeometryEnum
30215
+ });
30216
+ var NativeObjectDetectionSettingsPatchSchema = zod.z.object({ enabled: zod.z.boolean().optional() });
30003
30217
  var NativeObjectDetectionRuntimeStateSchema = NativeObjectDetectionStatusSchema.extend({
30004
30218
  /** Required by createRuntimeStateBridge — epoch ms of last refresh. */
30005
30219
  lastFetchedAt: zod.z.number() });
@@ -30009,13 +30223,28 @@ var nativeObjectDetectionCapability = {
30009
30223
  deviceNative: true,
30010
30224
  mode: "singleton",
30011
30225
  deviceTypes: [DeviceType.Camera],
30012
- methods: { setEnabled: method(zod.z.object({
30013
- deviceId: zod.z.number(),
30014
- enabled: zod.z.boolean()
30015
- }), zod.z.void(), {
30016
- kind: "mutation",
30017
- auth: "admin"
30018
- }) },
30226
+ deviceConfig: { ui: {
30227
+ kind: "derived-form",
30228
+ builderId: "native-object-detection",
30229
+ tab: "motion"
30230
+ } },
30231
+ methods: {
30232
+ getOptions: method(zod.z.object({ deviceId: zod.z.number() }), NativeObjectDetectionOptionsSchema),
30233
+ setSettings: method(zod.z.object({
30234
+ deviceId: zod.z.number(),
30235
+ settings: NativeObjectDetectionSettingsPatchSchema
30236
+ }), zod.z.void(), {
30237
+ kind: "mutation",
30238
+ auth: "admin"
30239
+ }),
30240
+ setEnabled: method(zod.z.object({
30241
+ deviceId: zod.z.number(),
30242
+ enabled: zod.z.boolean()
30243
+ }), zod.z.void(), {
30244
+ kind: "mutation",
30245
+ auth: "admin"
30246
+ })
30247
+ },
30019
30248
  events: { onDetected: { data: zod.z.object({
30020
30249
  deviceId: zod.z.number(),
30021
30250
  detection: NativeDetectionSchema
@@ -36969,6 +37198,7 @@ var ALL_CAPABILITY_DEFINITIONS = [
36969
37198
  streamBrokerCapability,
36970
37199
  streamCatalogCapability,
36971
37200
  streamParamsCapability,
37201
+ streamSignalsCapability,
36972
37202
  switchCapability,
36973
37203
  systemCapability,
36974
37204
  tamperCapability,
@@ -39747,12 +39977,24 @@ var METHOD_ACCESS_MAP = Object.freeze({
39747
39977
  addonId: null,
39748
39978
  access: "create"
39749
39979
  },
39980
+ "nativeObjectDetection.getOptions": {
39981
+ capName: "native-object-detection",
39982
+ capScope: "device",
39983
+ addonId: null,
39984
+ access: "view"
39985
+ },
39750
39986
  "nativeObjectDetection.setEnabled": {
39751
39987
  capName: "native-object-detection",
39752
39988
  capScope: "device",
39753
39989
  addonId: null,
39754
39990
  access: "create"
39755
39991
  },
39992
+ "nativeObjectDetection.setSettings": {
39993
+ capName: "native-object-detection",
39994
+ capScope: "device",
39995
+ addonId: null,
39996
+ access: "create"
39997
+ },
39756
39998
  "navigation.getFeatures": {
39757
39999
  capName: "navigation",
39758
40000
  capScope: "device",
@@ -42411,6 +42653,12 @@ var METHOD_ACCESS_MAP = Object.freeze({
42411
42653
  addonId: null,
42412
42654
  access: "create"
42413
42655
  },
42656
+ "streamBroker.reportStreamSignal": {
42657
+ capName: "stream-broker",
42658
+ capScope: "system",
42659
+ addonId: null,
42660
+ access: "create"
42661
+ },
42414
42662
  "streamBroker.restartProfile": {
42415
42663
  capName: "stream-broker",
42416
42664
  capScope: "system",
@@ -42495,6 +42743,12 @@ var METHOD_ACCESS_MAP = Object.freeze({
42495
42743
  addonId: null,
42496
42744
  access: "create"
42497
42745
  },
42746
+ "streamSignals.listSignals": {
42747
+ capName: "stream-signals",
42748
+ capScope: "device",
42749
+ addonId: null,
42750
+ access: "view"
42751
+ },
42498
42752
  "switch.setState": {
42499
42753
  capName: "switch",
42500
42754
  capScope: "device",
@@ -43967,11 +44221,21 @@ Object.freeze({
43967
44221
  form: "single",
43968
44222
  optional: false
43969
44223
  }],
44224
+ "nativeObjectDetection.getOptions": [{
44225
+ name: "deviceId",
44226
+ form: "single",
44227
+ optional: false
44228
+ }],
43970
44229
  "nativeObjectDetection.setEnabled": [{
43971
44230
  name: "deviceId",
43972
44231
  form: "single",
43973
44232
  optional: false
43974
44233
  }],
44234
+ "nativeObjectDetection.setSettings": [{
44235
+ name: "deviceId",
44236
+ form: "single",
44237
+ optional: false
44238
+ }],
43975
44239
  "navigation.getFeatures": [{
43976
44240
  name: "deviceId",
43977
44241
  form: "single",
@@ -44867,6 +45131,11 @@ Object.freeze({
44867
45131
  form: "single",
44868
45132
  optional: false
44869
45133
  }],
45134
+ "streamBroker.reportStreamSignal": [{
45135
+ name: "deviceId",
45136
+ form: "single",
45137
+ optional: false
45138
+ }],
44870
45139
  "streamBroker.restartProfile": [{
44871
45140
  name: "deviceId",
44872
45141
  form: "single",
@@ -45099,7 +45368,7 @@ Object.freeze({
45099
45368
  });
45100
45369
  /**
45101
45370
  * Per-cap runtime-state policy, projected from every `*.cap.ts` that declares
45102
- * `runtimeState`. 67 caps: 35 `restored`, 32 `session`.
45371
+ * `runtimeState`. 68 caps: 35 `restored`, 33 `session`.
45103
45372
  *
45104
45373
  * A cap absent from this map has no runtime-state slice at all. A cap PRESENT
45105
45374
  * with `durability: 'session'` has one and has decided not to keep it.
@@ -45325,6 +45594,10 @@ var RUNTIME_STATE_POLICY = {
45325
45594
  durability: "restored",
45326
45595
  volatileFields: ["lastFetchedAt"]
45327
45596
  },
45597
+ "stream-signals": {
45598
+ durability: "session",
45599
+ volatileFields: []
45600
+ },
45328
45601
  "switch": {
45329
45602
  durability: "restored",
45330
45603
  volatileFields: ["lastChangedAt"]
package/dist/index.js CHANGED
@@ -1,6 +1,6 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
2
  const require_chunk = require("./chunk-Cek0wNdY.js");
3
- const require_dist = require("./dist-DUSfUAgz.js");
3
+ const require_dist = require("./dist-D3lJV9M7.js");
4
4
  const require_builtins_alerts_alerts_addon = require("./builtins/alerts/alerts.addon.js");
5
5
  require("./builtins/alerts/index.js");
6
6
  const require_formatter = require("./formatter-DqAKDlvN.js");
@@ -29,7 +29,7 @@ const require_event_loop_stall_monitor = require("./event-loop-stall-monitor-CAs
29
29
  const require_tls$1 = require("./tls-BFs4PzoW.js");
30
30
  const require_manifest_system_deps = require("./manifest-system-deps-CrSP0Kkp.js");
31
31
  const require_resource_monitor = require("./resource-monitor-LVE1BoLs.js");
32
- const require_custom_action_registry = require("./custom-action-registry-DAFhWd0h.js");
32
+ const require_custom_action_registry = require("./custom-action-registry-CWFK5UDj.js");
33
33
  let zod = require("zod");
34
34
  let node_crypto = require("node:crypto");
35
35
  let _camstack_types_node = require("@camstack/types/node");
package/dist/index.mjs CHANGED
@@ -1,5 +1,5 @@
1
1
  import { a as __toCommonJS, i as __require, n as __esmMin, o as __toESM$1, r as __exportAll, t as __commonJSMin$1 } from "./chunk-CNf5ZN-e.mjs";
2
- import { A as addonSettingsCapability, Dt as procedureAuthKey, Ft as scopesAllowDeviceCap, Jt as errMsg$1, Mt as resolveMethodAuth, Pt as scopesAllowAddon, Qt as DEVICE_STATUS_METHOD, Xt as DATAPLANE_SECRET_HEADER$1, Zt as DEVICE_SETTINGS_CONTRIBUTION_METHODS, _ as RUNTIME_DEFAULTS, _n as readinessKey, _t as logLevelAtMost, an as asJsonObject$1, at as isArrayOutputSchema, cn as createEvent, ct as isObjectInput, dn as expandCapMethods, dt as isVoidInput, ft as kebabToCamel, gn as parseJsonUnknown, h as METHOD_ACCESS_MAP, hn as parseJsonObject, ln as emitDownForOwnedCaps, mt as lifecycleJobSchema, nn as ReadinessRegistry, on as asNumber, ot as isCollectionArrayMethod, p as LOG_LEVEL_RANK, rn as ReadinessTimeoutError, sn as asString$1, t as ALL_CAPABILITY_DEFINITIONS, tt as extractNestedAddonId, vn as resolveCapMount, vt as looseSchema, wt as objectInputDeclaresAddonId, xn as EventCategory$1, yn as scopeKey } from "./dist-Zhe9cTnK.mjs";
2
+ import { A as addonSettingsCapability, Dt as procedureAuthKey, Ft as scopesAllowDeviceCap, Jt as errMsg$1, Mt as resolveMethodAuth, Pt as scopesAllowAddon, Qt as DEVICE_STATUS_METHOD, Xt as DATAPLANE_SECRET_HEADER$1, Zt as DEVICE_SETTINGS_CONTRIBUTION_METHODS, _ as RUNTIME_DEFAULTS, _n as readinessKey, _t as logLevelAtMost, an as asJsonObject$1, at as isArrayOutputSchema, cn as createEvent, ct as isObjectInput, dn as expandCapMethods, dt as isVoidInput, ft as kebabToCamel, gn as parseJsonUnknown, h as METHOD_ACCESS_MAP, hn as parseJsonObject, ln as emitDownForOwnedCaps, mt as lifecycleJobSchema, nn as ReadinessRegistry, on as asNumber, ot as isCollectionArrayMethod, p as LOG_LEVEL_RANK, rn as ReadinessTimeoutError, sn as asString$1, t as ALL_CAPABILITY_DEFINITIONS, tt as extractNestedAddonId, vn as resolveCapMount, vt as looseSchema, wt as objectInputDeclaresAddonId, xn as EventCategory$1, yn as scopeKey } from "./dist-BIvXRexm.mjs";
3
3
  import { AlertCenterAddon } from "./builtins/alerts/alerts.addon.mjs";
4
4
  import "./builtins/alerts/index.mjs";
5
5
  import { t as formatLogLine } from "./formatter-B7qW8bPJ.mjs";
@@ -28,7 +28,7 @@ import { _ as fetchJson, a as createFileDataPlaneHandler, c as parseRangeHeader,
28
28
  import { C as registerLanHttpHandler, S as readLanHttpState, _ as DEFAULT_LAN_HTTP_PORT, a as writeTlsMode, b as bindPendingLanHttp, c as loadTlsCert, d as collectCertIdentity, f as CA_VALIDITY_DAYS, g as evaluateExistingCert, h as SERVER_AUTH_OID, i as writeExtraSans, l as reissueTlsLeaf, m as MAX_LEAF_VALIDITY_DAYS, n as readTlsAccessStatus, o as validateUploadedTls, p as LEAF_RENEWAL_WINDOW_DAYS, r as readTlsMode, s as ensureTlsCert, t as readExtraSans, u as CA_COMMON_NAME, v as allFamiliesListenHost, x as closeLanHttp, y as applyLanHttp } from "./tls-2xbgg78V.mjs";
29
29
  import { $ as buildUdsNativeCapProxy, $t as ARRAY_BUFFER_CENSUS_THRESHOLD_MB, A as createUdsLoggerWithControl, An as recordSocketFrame, At as HUB_MAIN_HEAP_WATCH_LABEL, B as readMoleculerFanoutMode, Bn as diffCapActionCensus, Bt as createLoopDelayMeter, C as ipcParentLink, Cn as createSocketPlaneMeter, Ct as runNpm, D as createUdsEventBus, Dn as readSocketQueues, Dt as HEAP_RECLAIM_TRIGGER_MB, E as createUdsEventBridge, En as formatSocketPlane, Et as HEAP_RECLAIM_STEADY_STATE_INTERVAL_MS, F as CapRouteError, Fn as CAP_ACTION_UNLABELLED, Ft as RUNNER_HEAP_WATCH_INTERVAL_MS, G as createLocalTransport, Gn as formatEventPlane, Gt as parseRssBudgetMb, H as PARENT_ROUTED_PROVIDER_ADDON_ID, Hn as createEventPlaneMeter, Ht as describeRss, I as classifyCapRoute, In as EMPTY_CAP_ACTION_READING, It as RUNNER_RSS_BUDGET_ENV, J as SocketChannel, Jt as startHeapWatch, K as UdsLocalTransportClient, Kt as reclaimIntervalMs, L as LocalChildClient, Ln as MAX_CAP_ACTIONS_PER_PEER, Lt as ZERO_LOOP_DELAY, M as AGENT_CAP_FWD_SERVICE, Mn as socketDirectionBytes, Mt as HUB_RSS_BUDGET_ENV, N as CapRouteResolver, Nn as socketDirectionMessages, Nt as RSS_BUDGET_REANNOUNCE_MIN_MS, O as udsChildLogToWorkerEntry, On as EMPTY_SOCKET_DIRECTION, Ot as HEAP_WATCH_INTERVAL_MS, P as callWithServiceDiscovery, Pn as CAP_ACTION_OVERFLOW_LABEL, Pt as RSS_BUDGET_RELEASE_RATIO, Q as buildNativeCapProxy, Qt as ARRAY_BUFFER_CENSUS_SUSTAIN_MS, R as LocalChildRegistry, Rn as capActionLabel, Rt as buildHeapSample, S as buildLinkChain, Sn as SOCKET_QUEUE_TOP_N, St as resolveNpmInvocation, T as createParentUnownedCallHandler, Tn as diffSocketPlane, Tt as HEAP_RECLAIM_MIN_INTERVAL_MS, U as __resetCapUsageRegistryForTests, Un as createEventPlaneReader, Ut as hubMainRssBudget, V as CapUsageRegistry, Vn as EVENT_PLANE_TOP_N, Vt as createV8Reclaimer, W as getCapUsageRegistry, Wn as diffEventPlane, Wt as nextRssBudgetState, X as FrameDecoder, Xt as strandedMb, Y as localEndpointPath, Yt as startRunnerHeapWatch, Z as encodeFrame, Zt as ARRAY_BUFFER_CENSUS_COOLDOWN_MS, _ as AddonDepsManager, _n as MESH_QUEUE_TOP_N, _t as CapabilityUnavailableError, a as createUdsAddonContext, an as DECISIVE_HEAP_SPACES, at as createAddonService, b as brokerCallForCap, bn as EMPTY_SOCKET_PLANE_BASELINE, bt as resolveAddonClass, c as getOrInitReadinessRegistryForClient, cn as formatHeapSpaces, ct as capActionName, d as clusterEventTopic, dn as selectReportedSpaces, dt as capServiceName, en as ARRAY_BUFFER_RELEASE_MIN_DROP_RATIO, et as createBrokerDeviceManagerApi, f as getBrokerEventBus, fn as OUTBOUND_QUEUE_COOLDOWN_MS, ft as parseCapAction, g as subscribePassthrough, gn as formatOutboundQueueVerdict, gt as CapabilityHandle, h as setNodeEventInterest, hn as createOutboundQueueGate, ht as DeviceRegistry, i as createAddonContext, in as isArrayBufferCensusReport, j as AGENT_CAP_FWD_ACTION, jn as sampleSocketDirection, jt as HUB_MAIN_RSS_BUDGET_MB, k as createUdsLogger, kn as createSocketDirectionCounters, kt as HEAP_WATCH_WARN_RATIO, l as setHubConnected, ln as heapSpaceField, lt as capActionSuffix, m as registerEventBusService, mn as OUTBOUND_QUEUE_THRESHOLD_BYTES, mt as serializeTypedArrays, n as installManifestPythonDeps, nn as createInspectorArrayBufferCensus, o as getWorkerDeviceRegistry, on as HEAP_SPACE_REPORT_MIN_MB, ot as validateProviderRegistrations, p as getMoleculerEventStats, pn as OUTBOUND_QUEUE_SUSTAIN_MS, pt as deserializeTypedArrays, q as UdsLocalTransportServer, qt as shouldReclaim, r as adaptBrokerToCluster, rn as formatArrayBufferCensus, rt as mountNativeCapService, s as getOrInitReadinessRegistry, sn as emitHeapDiagnosticReport, st as NATIVE_PROVIDER_SERVICE_INFIX, t as installManifestSystemDeps, tn as createArrayBufferCensusGate, u as EVENT_TOPIC_PREFIX, un as readHeapSpaces, ut as capBareAction, v as createKernelHwAccel, vn as createMeshQueueReader, vt as copyBundledNativeModules, w as localProviderLink, wn as createSocketPlaneReader, wt as createAddonDataPlaneFacility, x as brokerTransportLink, xn as SOCKET_PLANE_TOP_N, xt as buildNpmChildEnv, y as resolveHwAccel, yn as formatMeshQueue, yt as installManifestNativeDeps, z as UDS_NO_ROUTE_PREFIX, zn as createCapActionCensus, zt as createDeferredHeapWatchSink } from "./manifest-system-deps-BEj9Dpu1.mjs";
30
30
  import { n as getSinglePidStats, t as getPidStats } from "./resource-monitor-Vp0bPV3U.mjs";
31
- import { t as CustomActionRegistry } from "./custom-action-registry-CM03Xda0.mjs";
31
+ import { t as CustomActionRegistry } from "./custom-action-registry-CFHYDHAu.mjs";
32
32
  import { z } from "zod";
33
33
  import { createHash, randomUUID, timingSafeEqual } from "node:crypto";
34
34
  import { PYTHON_VERSION, buildBinaryPath, downloadBinary, ensureBinary, ensureFfmpeg, ensurePython, findInPath, getFfmpegDownloadUrl, getPlatformInfo, getPythonDownloadUrl, installPythonPackages, installPythonRequirements } from "@camstack/types/node";
@@ -1,4 +1,4 @@
1
- import { an as asJsonObject } from "./dist-Zhe9cTnK.mjs";
1
+ import { an as asJsonObject } from "./dist-BIvXRexm.mjs";
2
2
  //#region src/builtins/sqlite-storage/retired-settings-keys.ts
3
3
  /**
4
4
  * Is THIS node the one whose settings store is the cluster's authority?
@@ -1,4 +1,4 @@
1
- const require_dist = require("./dist-DUSfUAgz.js");
1
+ const require_dist = require("./dist-D3lJV9M7.js");
2
2
  //#region src/builtins/sqlite-storage/retired-settings-keys.ts
3
3
  /**
4
4
  * Is THIS node the one whose settings store is the cluster's authority?
@@ -0,0 +1,140 @@
1
+ import sharp from "sharp";
2
+ //#region src/builtins/snapshot/snapshot-courtesy.ts
3
+ /**
4
+ * State treatment for a camera that cannot produce a current frame.
5
+ *
6
+ * ── Why this exists ───────────────────────────────────────────────────────
7
+ * A disabled, offline or sleeping camera has no frame, and until 2026-08-11 the
8
+ * snapshot service simply had nothing to say about it: the media route answered
9
+ * **404** and the client painted "Unavailable". Measured on the live grid that
10
+ * day, 10 of 26 tiles were 404s — one genuinely disabled camera plus nine dead
11
+ * legacy rows. A 404 is indistinguishable from a broken camera, so an operator
12
+ * who deliberately switched a camera off saw the same thing as a fault ([D62]:
13
+ * "an off switch is REPORTED off; disabled must never look like broken").
14
+ *
15
+ * So the service answers with an image that SAYS what is going on, carrying
16
+ * the camera's own name. When a real cached frame exists it remains visible
17
+ * under the treatment; otherwise the generated courtesy background is used.
18
+ *
19
+ * ── Why sharp and not ffmpeg ──────────────────────────────────────────────
20
+ * The first draft of this file shelled out to `ffmpeg` with a `drawtext`
21
+ * filter. That was written before the resize path was measured, and it was the
22
+ * wrong call for the same reason: this runs in the snapshot wrapper, a system
23
+ * builtin loaded in the hub's ROOT process, so every render was a fork + exec
24
+ * on the loop that serves the API — 52 ms against sharp's 4.8 ms in that
25
+ * container, to draw two lines of text.
26
+ *
27
+ * `terminal-frame-renderer.ts` already renders text this way (SVG → sharp), so
28
+ * this is the house pattern rather than a new one. It also drops the font-PATH
29
+ * probing the ffmpeg version needed: an SVG names a font FAMILY and fontconfig
30
+ * resolves it — verified in the hub image, where `fc-match "DejaVu Sans Mono"`
31
+ * answers with the real file.
32
+ *
33
+ * Rendering is pure input → bytes with no I/O of its own, and the geometry and
34
+ * escaping are separated out so they stay testable without rasterizing.
35
+ */
36
+ /** Default tile size when the caller asks for no particular width. */
37
+ var COURTESY_DEFAULT_WIDTH = 640;
38
+ /**
39
+ * Font stack for the rendered text. Family names, not paths — librsvg resolves
40
+ * them through fontconfig, and the trailing generics keep a host without DejaVu
41
+ * rendering something legible instead of nothing.
42
+ */
43
+ var COURTESY_FONT_STACK = "DejaVu Sans,DejaVu Sans Mono,Helvetica,Arial,sans-serif";
44
+ /** The word the frame carries. Deliberately the operator's vocabulary. */
45
+ function courtesyLabel(reason) {
46
+ switch (reason) {
47
+ case "disabled": return "Disabled";
48
+ case "offline": return "Offline";
49
+ case "sleeping": return "Sleeping";
50
+ case "unreachable": return "Unreachable";
51
+ case "waking": return "Waking";
52
+ }
53
+ }
54
+ /**
55
+ * Background per reason. A disabled camera is a DELIBERATE state and must not
56
+ * read as an alarm, so it is neutral grey; offline is a fault and is warmer.
57
+ */
58
+ function courtesyBackground(reason) {
59
+ switch (reason) {
60
+ case "disabled": return "#2b2b31";
61
+ case "offline": return "#3a2b2b";
62
+ case "sleeping": return "#232b3a";
63
+ case "waking": return "#243247";
64
+ case "unreachable": return "#3a2b2b";
65
+ }
66
+ }
67
+ /** XML escaping for text placed inside an SVG `<text>` node. */
68
+ function escapeCourtesyText(value) {
69
+ return value.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll("\"", "&quot;").replaceAll("'", "&apos;");
70
+ }
71
+ /**
72
+ * The SVG the frame is rasterized from: the state large and centred, the
73
+ * camera's name under it, smaller and dimmer.
74
+ *
75
+ * Sizes derive from the width so a 240 px grid tile and a 1920 px full-bleed
76
+ * frame read the same, with a floor so a thumbnail stays legible.
77
+ */
78
+ function buildCourtesySvg(spec) {
79
+ return buildStateSvg(spec, false);
80
+ }
81
+ /**
82
+ * The same state treatment as a courtesy frame, but translucent so the last
83
+ * real camera frame remains visible underneath it.
84
+ */
85
+ function buildCourtesyOverlaySvg(spec) {
86
+ return buildStateSvg(spec, true);
87
+ }
88
+ function buildStateSvg(spec, overlay) {
89
+ const stateSize = Math.max(12, Math.round(spec.width / 12));
90
+ const nameSize = Math.max(9, Math.round(spec.width / 26));
91
+ const state = escapeCourtesyText(courtesyLabel(spec.reason));
92
+ const name = escapeCourtesyText(spec.deviceName);
93
+ const midY = spec.height / 2;
94
+ const washOpacity = overlay ? "0.62" : "1";
95
+ const textStroke = Math.max(1, Math.round(spec.width / 320));
96
+ return [
97
+ `<svg xmlns="http://www.w3.org/2000/svg" width="${String(spec.width)}" height="${String(spec.height)}">`,
98
+ `<rect width="100%" height="100%" fill="${courtesyBackground(spec.reason)}" fill-opacity="${washOpacity}"/>`,
99
+ `<g font-family="${COURTESY_FONT_STACK}" text-anchor="middle" paint-order="stroke" stroke="#111118" stroke-width="${String(textStroke)}">`,
100
+ `<text x="50%" y="${String(Math.round(midY))}" font-size="${String(stateSize)}" fill="#e8e8ee">${state}</text>`,
101
+ `<text x="50%" y="${String(Math.round(midY + stateSize))}" font-size="${String(nameSize)}" fill="#9a9aa8">${name}</text>`,
102
+ `</g></svg>`
103
+ ].join("");
104
+ }
105
+ /** Cache key — a courtesy frame is a pure function of these four. */
106
+ function courtesyCacheKey(spec) {
107
+ return `${spec.reason}:${String(spec.width)}x${String(spec.height)}:${spec.deviceName}`;
108
+ }
109
+ /**
110
+ * Render the frame.
111
+ *
112
+ * Rejects on failure — the caller decides what to do, exactly as `resizeJpeg`
113
+ * does, so a broken courtesy path is never mistaken for a broken camera.
114
+ */
115
+ function renderCourtesyJpeg(spec) {
116
+ return sharp(Buffer.from(buildCourtesySvg(spec))).jpeg({ quality: 82 }).toBuffer().then((bytes) => {
117
+ if (bytes.length === 0) throw new Error("courtesy frame produced no bytes");
118
+ return bytes;
119
+ });
120
+ }
121
+ /**
122
+ * Paint the state treatment over a real cached frame.
123
+ *
124
+ * The source dimensions are authoritative: overlays preserve the original
125
+ * frame rather than resizing it to the generated courtesy-frame default.
126
+ */
127
+ async function renderCourtesyOverlayJpeg(background, spec) {
128
+ const metadata = await sharp(background).metadata();
129
+ if (!metadata.width || !metadata.height) throw new Error("cached snapshot has no renderable dimensions");
130
+ const overlay = Buffer.from(buildCourtesyOverlaySvg({
131
+ ...spec,
132
+ width: metadata.width,
133
+ height: metadata.height
134
+ }));
135
+ const bytes = await sharp(background).composite([{ input: overlay }]).jpeg({ quality: 82 }).toBuffer();
136
+ if (bytes.length === 0) throw new Error("courtesy overlay produced no bytes");
137
+ return bytes;
138
+ }
139
+ //#endregion
140
+ export { renderCourtesyOverlayJpeg as i, courtesyCacheKey as n, renderCourtesyJpeg as r, COURTESY_DEFAULT_WIDTH as t };