@camstack/addon-provider-reolink 1.2.13 → 1.2.15

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 +451 -76
  2. package/dist/addon.mjs +451 -76
  3. package/package.json +3 -2
package/dist/addon.js CHANGED
@@ -26,7 +26,7 @@ let fs_promises = require("fs/promises");
26
26
  fs_promises = require_chunk.__toESM(fs_promises, 1);
27
27
  let node_os = require("node:os");
28
28
  node_os = require_chunk.__toESM(node_os);
29
- //#region ../types/dist/event-category-BLcNejAE.mjs
29
+ //#region ../types/dist/event-category-Bz24uP1U.mjs
30
30
  var EventCategory = /* @__PURE__ */ function(EventCategory) {
31
31
  EventCategory["SystemBoot"] = "system.boot";
32
32
  EventCategory["SystemAddonsReady"] = "system.addons-ready";
@@ -297,6 +297,19 @@ var EventCategory = /* @__PURE__ */ function(EventCategory) {
297
297
  */
298
298
  EventCategory["DeviceStateChanged"] = "device.state-changed";
299
299
  /**
300
+ * Frame occupancy for a camera CHANGED — a tracked object was gained or
301
+ * lost. Carries `{ deviceId, totalObjects, byClass, zones }`.
302
+ *
303
+ * Emitted only on a change, so a steady scene is silent. It exists so a
304
+ * client can stop polling `zoneAnalytics.getCurrentSnapshot`: that was the
305
+ * one live badge with no push signal at all, and it cost a request every
306
+ * four seconds per visible camera.
307
+ *
308
+ * Like every event it is telemetry and may be dropped ([D8]) — a consumer
309
+ * keeps a slow reconcile rather than trusting it alone.
310
+ */
311
+ EventCategory["ZoneAnalyticsOccupancyChanged"] = "zone-analytics.occupancy-changed";
312
+ /**
300
313
  * Cap event fired by every device that registers the `battery`
301
314
  * capability. Mirrors the cap definition's `onStatusChanged`. Carries
302
315
  * `{ deviceId, status: BatteryStatus }`. Subscribers (alert center,
@@ -7413,35 +7426,22 @@ function findTimezone(id) {
7413
7426
  */
7414
7427
  var RecordingWeekdaySchema = number().int().min(0).max(6);
7415
7428
  var HHMM = /^([01]\d|2[0-3]):[0-5]\d$/;
7416
- var RecordingScheduleSchema = discriminatedUnion("kind", [object({ kind: literal("always") }), object({
7417
- kind: literal("timeOfDay"),
7418
- start: string().regex(HHMM),
7419
- end: string().regex(HHMM),
7420
- /** Restrict to these weekdays; omit = every day. */
7421
- days: array(RecordingWeekdaySchema).optional()
7422
- })]);
7423
- var RecordingModeSchema = _enum([
7424
- "continuous",
7425
- "onMotion",
7426
- "onAudioThreshold"
7427
- ]);
7428
7429
  /**
7429
- * First-class, authoritative per-camera storage mode — the explicit choice the
7430
- * UI reads directly (never inferred from `rules`):
7431
- * - `off` — not recording.
7432
- * - `events` — record only around triggers (motion / audio threshold),
7433
- * with pre/post-buffer.
7434
- * - `continuous` — record 24/7 within the schedule.
7430
+ * DERIVED per-camera storage summary — the single field cheap consumers read
7431
+ * (the viewer's status dot, the camera list) instead of walking `bands`:
7432
+ * - `off` — no band covers the camera (or it is disabled).
7433
+ * - `events` — every band records around triggers only.
7434
+ * - `continuous` — at least one band records continuously.
7435
7435
  *
7436
- * `mode` compiles one-way to the internal `rules[]` consumed by the policy
7437
- * engine (see `compileRules`); `rules[]` is never authored directly anymore.
7436
+ * NEVER authored: the recorder stamps it from the authoritative `bands` on
7437
+ * every save (`activeModeForConfig`). Writing it has no effect.
7438
7438
  */
7439
7439
  var RecordingStorageModeSchema = _enum([
7440
7440
  "off",
7441
7441
  "events",
7442
7442
  "continuous"
7443
7443
  ]);
7444
- /** Which detectors trigger an `events`-mode recording. */
7444
+ /** Which detectors trigger an `events`-mode band. */
7445
7445
  var RecordingTriggersSchema = object({
7446
7446
  motion: boolean().optional(),
7447
7447
  audioThresholdDbfs: number().optional()
@@ -7477,18 +7477,6 @@ var RecordingBandSchema = object({
7477
7477
  preBufferSec: number().min(0).optional(),
7478
7478
  postBufferSec: number().min(0).optional()
7479
7479
  });
7480
- var RecordingRuleSchema = object({
7481
- schedule: RecordingScheduleSchema,
7482
- mode: RecordingModeSchema,
7483
- /** Seconds of footage to retain BEFORE a trigger (applied at keep/discard). */
7484
- preBufferSec: number().min(0).default(0),
7485
- /** Keep recording until this many seconds after the last trigger. */
7486
- postBufferSec: number().min(0).default(0),
7487
- /** Each new trigger restarts the post-buffer window. */
7488
- resetTimeoutOnNewEvent: boolean().default(true),
7489
- /** onAudioThreshold only — dBFS level that counts as a trigger. */
7490
- thresholdDbfs: number().optional()
7491
- });
7492
7480
  /**
7493
7481
  * Per-device retention overrides. Every field is optional; an unset or `0`
7494
7482
  * value inherits the node-wide recorder default. Only footage-lifetime limits
@@ -7522,40 +7510,28 @@ var ScrubThumbnailPresetSchema = _enum([
7522
7510
  /**
7523
7511
  * The full per-camera recording intent — the wire shape of a RecordingTarget.
7524
7512
  *
7525
- * `mode` is the authoritative storage choice; `schedule`/`triggers`/`pre`/`post`
7526
- * are its mode-specific parameters. `rules` is a DEPRECATED authoring input kept
7527
- * only for transition + migration (`migrateRulesToMode`); the policy engine
7528
- * consumes the compiled output of `compileRules(config)`, never `rules` directly.
7513
+ * `bands` is the ONLY authored recording intent: what to record, when, and on
7514
+ * which trigger. `mode` is a derived summary the recorder stamps on save; every
7515
+ * other field is a storage knob (profiles, segment length, retention, scrub).
7516
+ *
7517
+ * STRICT on purpose: the legacy authoring surface (`schedule`/`schedules`/
7518
+ * `triggers`/`preBufferSec`/`postBufferSec`/`rules`) was retired 2026-07-30.
7519
+ * A stale caller must fail loudly — silently stripping its legacy intent would
7520
+ * persist a band-less config, i.e. silently stop recording the camera.
7529
7521
  */
7530
7522
  var RecordingConfigSchema = object({
7531
7523
  enabled: boolean(),
7532
- /** Authoritative storage mode. Absent on legacy targets derived once via
7533
- * `migrateRulesToMode`, then persisted. */
7524
+ /** DERIVED summary of `bands`, stamped by the recorder on every save.
7525
+ * Authoring it has no effect — see {@link RecordingStorageModeSchema}. */
7534
7526
  mode: RecordingStorageModeSchema.optional(),
7535
7527
  profiles: array(CamProfileSchema).optional(),
7536
7528
  segmentSeconds: number().int().positive().optional(),
7537
- /** Shared recording time-bands for `events` & `continuous` — record only when
7538
- * the wall-clock falls inside one of these windows. Omit or empty = always.
7539
- * `continuous` compiles to one rule per band; `events` to band × trigger. */
7540
- schedules: array(RecordingScheduleSchema).optional(),
7541
- /** Legacy single-band predecessor of `schedules`. Read-compat only — it is
7542
- * normalized into `schedules` on read and never written going forward. (Not
7543
- * tagged `@deprecated`: the normalization paths must read it cast-free.) */
7544
- schedule: RecordingScheduleSchema.optional(),
7545
- /** `events`-mode only — which detectors trigger a recording. */
7546
- triggers: RecordingTriggersSchema.optional(),
7547
- /** `events`-mode only — seconds retained before / after a trigger. */
7548
- preBufferSec: number().min(0).optional(),
7549
- postBufferSec: number().min(0).optional(),
7550
- /** DEPRECATED authoring input; retained for migration/transition. */
7551
- rules: array(RecordingRuleSchema).optional(),
7552
7529
  /**
7553
- * AUTHORITATIVE mode-per-band recording model (recorder). When present it
7554
- * is the single source of truth; the legacy `mode`/`schedules`/`schedule`/
7555
- * `triggers`/`rules` fields above are kept for READ-COMPAT only and are
7556
- * derived into bands once via `migrateConfigToBands`.
7530
+ * AUTHORITATIVE mode-per-band recording model the single source of truth
7531
+ * the recorder's band engine consumes. An empty array = record nothing;
7532
+ * "off" is the absence of a covering band, never a band value.
7557
7533
  */
7558
- bands: array(RecordingBandSchema).optional(),
7534
+ bands: array(RecordingBandSchema).default([]),
7559
7535
  retention: RecordingRetentionSchema.optional(),
7560
7536
  /**
7561
7537
  * Per-camera scrub-thumbnail fidelity preset (resolution + JPEG quality for
@@ -7563,8 +7539,15 @@ var RecordingConfigSchema = object({
7563
7539
  * windows only — existing sheets are immutable, and each window's index
7564
7540
  * carries its own tile dims so mixed-preset history renders correctly.
7565
7541
  */
7566
- scrubThumbnails: ScrubThumbnailPresetSchema.optional()
7567
- });
7542
+ scrubThumbnails: ScrubThumbnailPresetSchema.optional(),
7543
+ /**
7544
+ * OPT-IN thumbnail-strip generation for this camera: every keyframe of the
7545
+ * low recording saved as a JPEG (the fast-drag scrub depth), a derived
7546
+ * cache that eviction reclaims with the footage. Absent/false = no strips
7547
+ * are written and scrub reads exact keyframes at every velocity.
7548
+ */
7549
+ stripsEnabled: boolean().optional()
7550
+ }).strict();
7568
7551
  /**
7569
7552
  * Ops-log — the durable, append-only operations audit shared by the
7570
7553
  * recordings and events management surfaces.
@@ -7583,7 +7566,8 @@ var OpsLogOpSchema = _enum([
7583
7566
  "prune",
7584
7567
  "manual-delete",
7585
7568
  "rescan",
7586
- "retention-run"
7569
+ "retention-run",
7570
+ "relocate"
7587
7571
  ]);
7588
7572
  /** Why the operation ran. */
7589
7573
  var OpsLogReasonSchema = _enum([
@@ -7622,6 +7606,55 @@ var OpsLogQueryInputSchema = object({
7622
7606
  limit: number().int().min(1).max(1e3).optional()
7623
7607
  });
7624
7608
  /**
7609
+ * Entity-relocation job state (storage entity-routing spec, Phase 4).
7610
+ *
7611
+ * One shape shared by the recorder's `relocateFootage` (segments + strips) and
7612
+ * pipeline-analytics' `relocateMedia` (event media blobs) so the admin Data
7613
+ * page renders both movers with one component. Jobs are in-RAM (a restart
7614
+ * forgets them — re-running is safe by construction: copy-if-absent, delete
7615
+ * after verify) and each completed/failed run also lands one durable ops-log
7616
+ * row on the owning addon's surface.
7617
+ */
7618
+ var RelocateJobStateSchema = _enum([
7619
+ "running",
7620
+ "done",
7621
+ "failed",
7622
+ "cancelled"
7623
+ ]);
7624
+ var RelocateJobSchema = object({
7625
+ jobId: string(),
7626
+ state: RelocateJobStateSchema,
7627
+ /** Source location — for media relocation this is informational ('*': rows
7628
+ * move from wherever they are to the target). */
7629
+ fromLocationId: string(),
7630
+ toLocationId: string(),
7631
+ /** Scoped device, or null = every device. */
7632
+ deviceId: number().nullable(),
7633
+ /** What the job moves (owner-addon specific: segments/strips or media). */
7634
+ entities: array(string()),
7635
+ filesMoved: number().int(),
7636
+ bytesMoved: number().int(),
7637
+ /** Total files discovered up front; null while (or when) unknown. */
7638
+ filesTotal: number().int().nullable(),
7639
+ startedAt: number(),
7640
+ finishedAt: number().nullable(),
7641
+ error: string().nullable()
7642
+ });
7643
+ var RelocateFootageInputSchema = object({
7644
+ deviceId: number().optional(),
7645
+ fromLocationId: string(),
7646
+ toLocationId: string(),
7647
+ entities: array(_enum(["segments", "strips"])).optional(),
7648
+ /** Copy throttle in MB/s (default 40) — the drain is a background chore,
7649
+ * never allowed to starve live writers. */
7650
+ throttleMbps: number().min(1).max(1e3).optional()
7651
+ });
7652
+ var RelocateMediaInputSchema = object({
7653
+ deviceId: number().optional(),
7654
+ toLocationId: string(),
7655
+ throttleMbps: number().min(1).max(1e3).optional()
7656
+ });
7657
+ /**
7625
7658
  * `StorageLocationType` — an addon-declared id that identifies the *kind* of
7626
7659
  * storage a location serves. Defined here (not in `capabilities/storage.cap.ts`)
7627
7660
  * so the persisted record schema and the consumer-facing cap can both consume it
@@ -7671,6 +7704,13 @@ var StorageLocationSchema = object({
7671
7704
  nodeId: string().optional(),
7672
7705
  isDefault: boolean().default(false),
7673
7706
  isSystem: boolean().default(false),
7707
+ /** COMPUTED at read time by the orchestrator (statfs of the backing volume
7708
+ * for node-local locations it can reach) — never persisted, absent when the
7709
+ * volume is remote/unreachable. The single capacity truth every UI reads. */
7710
+ capacity: object({
7711
+ totalBytes: number(),
7712
+ availableBytes: number()
7713
+ }).nullable().optional(),
7674
7714
  createdAt: number(),
7675
7715
  updatedAt: number()
7676
7716
  });
@@ -8438,7 +8478,8 @@ var NcTaxonomyEntrySchema = object({
8438
8478
  /** Macro/category parent for grouping ('car' → 'vehicle'); null for a top. */
8439
8479
  parentKind: string().nullable()
8440
8480
  });
8441
- object({
8481
+ /** The complete NC picker taxonomy — three grouped buckets. */
8482
+ var NcTaxonomySchema = object({
8442
8483
  videoClasses: array(NcTaxonomyEntrySchema),
8443
8484
  audioKinds: array(NcTaxonomyEntrySchema),
8444
8485
  labels: array(NcTaxonomyEntrySchema)
@@ -9380,6 +9421,7 @@ function createRuntimeStateBridge(params) {
9380
9421
  getStatus
9381
9422
  };
9382
9423
  }
9424
+ new Set(["devices", "classes"]);
9383
9425
  /**
9384
9426
  * Shared geometry vocabulary for on-frame shape caps — privacy-mask,
9385
9427
  * motion-zones, and the detection zones/lines editor all speak this one
@@ -9538,6 +9580,29 @@ var NcOccupancyConditionSchema = object({
9538
9580
  count: number().int().min(0).default(1),
9539
9581
  sustainSeconds: number().int().min(0).max(3600).default(15)
9540
9582
  });
9583
+ /**
9584
+ * Which zone-crossing DIRECTION a rule accepts (`ObjectEvent.zoneCrossing`).
9585
+ *
9586
+ * The values are not symmetric, and deliberately so — the absent value has to
9587
+ * mean exactly what every rule authored before this condition existed already
9588
+ * does:
9589
+ * - `enter` — entries and every NON-crossing record (movement state,
9590
+ * package, sensor). Exits are rejected. **This is the absent behaviour**:
9591
+ * an operator who never asked for exits must not start receiving them.
9592
+ * - `exit` — ONLY an exit crossing. A record that is not a crossing at all
9593
+ * fails closed, because "the car left the drive" is a question about a
9594
+ * boundary, not about a detection.
9595
+ * - `any` — no direction filter; entries, exits and non-crossings alike.
9596
+ *
9597
+ * A rule asking for a direction should normally also scope `zones`, which the
9598
+ * engine evaluates against the crossed zone as well as the current membership
9599
+ * (an exit's membership no longer contains the zone it just left).
9600
+ */
9601
+ var NcCrossingSchema = _enum([
9602
+ "enter",
9603
+ "exit",
9604
+ "any"
9605
+ ]);
9541
9606
  /** Admin-zone membership condition (zone IDs as stamped by the ZoneEngine). */
9542
9607
  var NcZoneConditionSchema = object({
9543
9608
  ids: array(string().min(1)).min(1),
@@ -9562,6 +9627,13 @@ var NcConditionsSchema = object({
9562
9627
  /** Veto zones — any hit fails the rule. */
9563
9628
  zonesExclude: array(string().min(1)).optional(),
9564
9629
  /**
9630
+ * Zone-crossing direction. IMMEDIATE only — a crossing is a per-event fact
9631
+ * and a closed track carries none, so a `track-end` rule asking for one
9632
+ * fails closed (use `zones`, which tests `zonesVisited`). ABSENT = `enter`,
9633
+ * which is exactly today's behaviour. See {@link NcCrossingSchema}.
9634
+ */
9635
+ crossing: NcCrossingSchema.optional(),
9636
+ /**
9565
9637
  * Exact (case-insensitive) match on the record's collapsed `label`
9566
9638
  * (identity name / plate text / subclass).
9567
9639
  */
@@ -9696,17 +9768,85 @@ var NcRuleTargetSchema = object({
9696
9768
  * - `keyFrame` — the clean scene frame (no subject box).
9697
9769
  * - `none` — no attachment.
9698
9770
  */
9699
- var NcMediaPolicySchema = object({ attach: _enum([
9700
- "best",
9701
- "best-matching",
9702
- "keyFrame",
9703
- "none"
9704
- ]).default("best") });
9771
+ /**
9772
+ * WHAT THE PICTURE SHOWS — chosen explicitly, because the implicit ladders
9773
+ * conflate it with the selection strategy and betray the request: asking for
9774
+ * the clean scene frame on an object-event owner used to start at
9775
+ * `fullFrameBoxed`, i.e. the annotated frame (2026-07-30).
9776
+ * - `cropped` — the subject, tight. What "who is at the door" wants.
9777
+ * - `full` — the clean scene, no annotation. What "what is going on" wants.
9778
+ * - `boxed` — the scene WITH the detection boxes drawn, for verifying what
9779
+ * the pipeline actually saw.
9780
+ */
9781
+ var NcMediaFrameSchema = _enum([
9782
+ "cropped",
9783
+ "full",
9784
+ "boxed"
9785
+ ]);
9786
+ var NcMediaPolicySchema = object({
9787
+ attach: _enum([
9788
+ "best",
9789
+ "best-matching",
9790
+ "keyFrame",
9791
+ "none"
9792
+ ]).default("best"),
9793
+ /** What the still shows. Absent = `cropped` (the historical `best` shape). */
9794
+ frame: NcMediaFrameSchema.optional(),
9795
+ /** Crop the attached still to the rule's condition-zone bbox (padded) —
9796
+ * "show me the ZONE", not the whole scene or the subject crop. */
9797
+ zoneCrop: boolean().optional(),
9798
+ /**
9799
+ * Also attach a short GIF cut from the stream broker's clip ring around the
9800
+ * event — NOT from the recording, so the camera does not have to be
9801
+ * recording, and the window sits AROUND the moment instead of a segment
9802
+ * behind it. Fail-closed: a window the ring does not cover contributes no
9803
+ * gif, never a failed notification.
9804
+ */
9805
+ gif: boolean().optional(),
9806
+ /**
9807
+ * Also attach a short MP4 CLIP of the same cut. Same source and the same
9808
+ * fail-closed rule as `gif`. Prefer it where the backend takes video
9809
+ * (telegram, discord, zentik, webhook); backends that do not are handled by
9810
+ * the degrade engine, which drops the video and keeps the still.
9811
+ */
9812
+ clip: boolean().optional(),
9813
+ /** Seconds of footage BEFORE / AFTER the event instant. Defaults 3 / 7. */
9814
+ clipPreRollSec: number().int().min(0).max(30).optional(),
9815
+ clipPostRollSec: number().int().min(0).max(30).optional(),
9816
+ /**
9817
+ * Which stream profile the footage is cut from. Absent = the CHEAPEST
9818
+ * assigned profile: a notification is watched on a phone, so the 4K
9819
+ * rendition would burn CPU to produce a file the client downscales anyway.
9820
+ * A profile that is not assigned falls back to the cheapest, and the render
9821
+ * reports which one actually ran.
9822
+ */
9823
+ profile: CamProfileSchema.optional()
9824
+ });
9825
+ /**
9826
+ * Cooldown GRANULARITY over the subject's class — how much a fired
9827
+ * notification suppresses.
9828
+ * - `shared` (default, and the absent value) — one window for the whole
9829
+ * rule/scope: a cat silences the next dog for `cooldownSec`.
9830
+ * - `per-class` — an independent window per detected class, so cat→dog fires
9831
+ * at once and cat→cat still waits.
9832
+ *
9833
+ * AUDIO subjects are ALWAYS per-class regardless of this setting: a scream
9834
+ * must not be swallowed by a bark's window (the precedent this generalizes —
9835
+ * see `cooldownKey` in the rule engine).
9836
+ */
9837
+ var NcThrottleGranularitySchema = _enum(["shared", "per-class"]);
9705
9838
  /** Throttle — cooldown survives restarts (rebuilt from the outbox on boot). */
9706
9839
  var NcThrottleSchema = object({
9707
9840
  cooldownSec: number().int().min(0).max(86400).default(60),
9708
9841
  /** `rule` = one shared cooldown; `rule-device` = per-camera cooldown. */
9709
- scope: _enum(["rule", "rule-device"]).default("rule-device")
9842
+ scope: _enum(["rule", "rule-device"]).default("rule-device"),
9843
+ /**
9844
+ * Class granularity of the cooldown key. Optional rather than defaulted:
9845
+ * a Zod default does NOT run on the addon→addon cap path, so a persisted
9846
+ * rule authored before this field simply carries none — and the engine
9847
+ * reads absent as `shared`, the pre-existing behaviour.
9848
+ */
9849
+ granularity: NcThrottleGranularitySchema.optional()
9710
9850
  });
9711
9851
  /** Client-supplied rule fields (server stamps id/createdBy/createdAt/updatedAt). */
9712
9852
  var NcRuleInputSchema = object({
@@ -9715,7 +9855,19 @@ var NcRuleInputSchema = object({
9715
9855
  delivery: NcDeliverySchema,
9716
9856
  conditions: NcConditionsSchema.default({}),
9717
9857
  schedule: NcScheduleSchema.optional(),
9718
- targets: array(NcRuleTargetSchema).min(1),
9858
+ /** May be empty when `targetUsers` addresses at least one user — the
9859
+ * "at least one addressee" invariant is enforced by the provider, because
9860
+ * a cross-field refine here would break `NcRulePatchSchema.partial()`. */
9861
+ targets: array(NcRuleTargetSchema),
9862
+ /**
9863
+ * USERS this rule addresses in addition to `targets` (Phase 4). At fire
9864
+ * time each user fans out to the personal targets they own
9865
+ * (`config.ownerUserId`), filtered by that user's `allowedDevices` for the
9866
+ * firing camera — a user is never notified about a device they cannot open.
9867
+ * Per-user opt-outs (`disabledTargetIds`) still apply to the fanned-out
9868
+ * targets.
9869
+ */
9870
+ targetUsers: array(string()).optional(),
9719
9871
  media: NcMediaPolicySchema.default({ attach: "best" }),
9720
9872
  throttle: NcThrottleSchema.default({
9721
9873
  cooldownSec: 60,
@@ -9802,6 +9954,7 @@ var NcConditionDescriptorSchema = object({
9802
9954
  "schedule",
9803
9955
  "plateMatcher",
9804
9956
  "packagePhase",
9957
+ "crossingSelect",
9805
9958
  "polygonDraw",
9806
9959
  "occupancy"
9807
9960
  ]),
@@ -9928,7 +10081,10 @@ method(object({}), object({ rules: array(NcRuleSchema) }), { auth: "admin" }), m
9928
10081
  }), object({ results: array(NcTestResultSchema) }), {
9929
10082
  kind: "mutation",
9930
10083
  auth: "admin"
9931
- }), method(object({}), object({ catalog: array(NcConditionDescriptorSchema) })), method(object({ filter: NcHistoryFilterSchema.default({ limit: 100 }) }), object({ entries: array(NcHistoryEntrySchema) }), { auth: "admin" });
10084
+ }), method(object({}), object({
10085
+ catalog: array(NcConditionDescriptorSchema),
10086
+ taxonomy: NcTaxonomySchema.optional()
10087
+ })), method(object({ filter: NcHistoryFilterSchema.default({ limit: 100 }) }), object({ entries: array(NcHistoryEntrySchema) }), { auth: "admin" });
9932
10088
  /**
9933
10089
  * TimelapseRule — the STANDALONE scheduled timelapse producer's rule model.
9934
10090
  *
@@ -10937,6 +11093,28 @@ method(object({
10937
11093
  }), object({ success: literal(true) }), {
10938
11094
  kind: "mutation",
10939
11095
  auth: "admin"
11096
+ }), method(object({
11097
+ deviceId: number(),
11098
+ /** Absent = the LOWEST assigned profile — a notification attachment is
11099
+ * watched on a phone, and the cheap rendition is the right default. */
11100
+ profile: CamProfileSchema.optional(),
11101
+ aroundMs: number(),
11102
+ preRollSec: number().min(0).max(20).default(3),
11103
+ postRollSec: number().min(0).max(20).default(5),
11104
+ format: _enum(["gif", "mp4"]).default("gif"),
11105
+ maxWidth: number().int().min(120).max(1920).default(480),
11106
+ /** GIF only — MP4 keeps the source cadence. */
11107
+ fps: number().int().min(1).max(15).default(5)
11108
+ }), object({
11109
+ base64: string(),
11110
+ mime: string(),
11111
+ bytes: number().int(),
11112
+ /** The profile actually rendered (what the default resolved to). */
11113
+ profile: CamProfileSchema,
11114
+ durationMs: number()
11115
+ }), {
11116
+ kind: "mutation",
11117
+ auth: "admin"
10940
11118
  }), method(_void(), array(CameraStreamSchema).readonly()), method(_void(), array(ProfileSlotSchema).readonly()), method(object({ brokerId: string() }), BrokerStatsSchema), method(object({ brokerId: string() }), object({
10941
11119
  probed: boolean(),
10942
11120
  summary: string()
@@ -19011,7 +19189,10 @@ method(object({
19011
19189
  }), method(object({
19012
19190
  deviceId: number(),
19013
19191
  caps: array(string()).readonly().optional()
19014
- }), record(string(), unknown().nullable()));
19192
+ }), record(string(), unknown().nullable())), method(object({
19193
+ deviceIds: array(number()).readonly(),
19194
+ caps: array(string()).readonly().optional()
19195
+ }), record(string(), record(string(), unknown().nullable())));
19015
19196
  method(object({ deviceId: number() }), record(string(), record(string(), unknown()))), method(object({
19016
19197
  deviceId: number(),
19017
19198
  capName: string()
@@ -19942,6 +20123,36 @@ var TargetKindSchema = object({
19942
20123
  icon: string(),
19943
20124
  /** Stamped by each provider so the concat-fanned catalog stays routable. */
19944
20125
  addonId: string(),
20126
+ /**
20127
+ * URL of the kind's bundled BRAND icon, served by the providing addon over
20128
+ * its own `addon-routes` surface (`/addon/<addonId>/icons/<kind>`). Absent
20129
+ * when the addon bundles no icon for that kind — the client then falls back
20130
+ * to a neutral glyph rather than rendering the raw `icon` NAME as text.
20131
+ *
20132
+ * Root-relative on purpose: it resolves against whatever origin serves a web
20133
+ * client, and a native client joins it onto its own hub base.
20134
+ *
20135
+ * DECLARED here deliberately. It used to travel as an undeclared passthrough
20136
+ * field that survived only because the runtime cap-router forwards provider
20137
+ * output verbatim — so every consumer had to re-declare it by hand to stop
20138
+ * its own Zod parse from stripping it, and the whole arrangement would have
20139
+ * broken silently the moment output validation was tightened anywhere.
20140
+ */
20141
+ iconUrl: string().optional(),
20142
+ /**
20143
+ * Media type of {@link iconUrl} (`image/svg+xml`, `image/png`, …).
20144
+ *
20145
+ * The server knows this and therefore says it, because the client cannot
20146
+ * safely guess: a React-Native client renders SVG and raster through two
20147
+ * DIFFERENT components (`react-native-svg` vs `expo-image` — expo-image does
20148
+ * not decode SVG on iOS/Android), so without this it silently fell back to a
20149
+ * placeholder glyph for every vector icon while the web build looked fine.
20150
+ *
20151
+ * Absent when {@link iconUrl} is absent, or for a legacy provider that has
20152
+ * not been updated — a client that cannot determine the type should prefer
20153
+ * its raster path, which is the safe default for an unknown image.
20154
+ */
20155
+ iconMediaType: string().optional(),
19945
20156
  configSchema: ConfigSchemaPassthrough,
19946
20157
  supportsDiscovery: boolean(),
19947
20158
  caps: TargetKindCapsSchema
@@ -20389,6 +20600,29 @@ var MotionEventSchema = object({
20389
20600
  * Absent on legacy rows ⇒ treat as `pipeline`.
20390
20601
  */
20391
20602
  var DetectionSourceSchema = _enum(["pipeline", "onboard"]);
20603
+ /**
20604
+ * The confirmed zone crossing that produced an object event. Present ONLY on
20605
+ * an event emitted BY a crossing (`zone.enter` / `zone.exit`); a movement-state
20606
+ * event (`object.entering` / `leaving` / `stationary` / `loitering`) and an
20607
+ * appearance event carry none, so a rule asking for a direction fails closed
20608
+ * on them.
20609
+ *
20610
+ * Exactly ONE crossing per event: the emitter turns each confirmed crossing
20611
+ * into its own event, so a frame in which a track enters A while leaving B
20612
+ * produces two events with two directions — never one ambiguous row.
20613
+ *
20614
+ * `zoneId` is load-bearing for an EXIT: the event's `zones` list is the
20615
+ * membership the box has NOW, and by definition it no longer contains the zone
20616
+ * that was just left. Without the id here, a zone-scoped rule could never match
20617
+ * the exit it asked for.
20618
+ */
20619
+ var ZoneCrossingSchema = object({
20620
+ direction: _enum(["enter", "exit"]),
20621
+ /** Admin zone id crossed. */
20622
+ zoneId: string(),
20623
+ /** Zone display name at crossing time (falls back to the id). */
20624
+ zoneName: string().optional()
20625
+ });
20392
20626
  var ObjectEventSchema = object({
20393
20627
  ...BaseEventFields,
20394
20628
  kind: literal("object"),
@@ -20415,6 +20649,12 @@ var ObjectEventSchema = object({
20415
20649
  zones: array(string()).readonly().optional(),
20416
20650
  /** Omitted in slim projection. */
20417
20651
  state: TrackStateSchema.optional(),
20652
+ /**
20653
+ * The zone crossing this event IS, when it is one. Absent on every other
20654
+ * event kind (movement state, appearance, package) — see
20655
+ * {@link ZoneCrossingSchema}. Omitted in slim projection.
20656
+ */
20657
+ zoneCrossing: ZoneCrossingSchema.optional(),
20418
20658
  /** Detection-frame dimensions in pixels — let consumers normalize the
20419
20659
  * pixel-space `bbox` onto a displayed image. Omitted in slim projection. */
20420
20660
  frameWidth: number().optional(),
@@ -20673,6 +20913,15 @@ DeviceType.Camera, method(object({ deviceId: number() }), array(TrackSchema).rea
20673
20913
  }), method(object({ deviceId: number() }), EventPruneCountsSchema, {
20674
20914
  kind: "mutation",
20675
20915
  auth: "admin"
20916
+ }), method(RelocateMediaInputSchema, object({ jobId: string() }), {
20917
+ kind: "mutation",
20918
+ auth: "admin"
20919
+ }), method(object({}), array(RelocateJobSchema).readonly(), {
20920
+ kind: "query",
20921
+ auth: "admin"
20922
+ }), method(object({ jobId: string() }), object({ cancelled: boolean() }), {
20923
+ kind: "mutation",
20924
+ auth: "admin"
20676
20925
  }), method(OpsLogQueryInputSchema, array(OpsLogEntrySchema).readonly(), {
20677
20926
  kind: "query",
20678
20927
  auth: "admin"
@@ -23254,6 +23503,17 @@ var GetConnectionEndpointsResultSchema = object({ endpoints: array(object({
23254
23503
  */
23255
23504
  priority: number()
23256
23505
  })).readonly() });
23506
+ /**
23507
+ * The chosen outbound endpoint for notification artifacts. `baseUrl: null` =
23508
+ * AUTO (resolved from the candidate ranking at send time); `resolved` reports
23509
+ * what AUTO currently picks, so the UI can show the effective value either way.
23510
+ */
23511
+ var NotificationEndpointSchema = object({
23512
+ /** The operator's explicit choice, or null for AUTO. */
23513
+ baseUrl: string().nullable(),
23514
+ /** What the ranking currently resolves to (null when nothing is reachable). */
23515
+ resolved: string().nullable()
23516
+ });
23257
23517
  var AllowedAddressesSchema = object({
23258
23518
  /**
23259
23519
  * Allowlist of interface addresses operators have explicitly opted
@@ -23276,7 +23536,7 @@ method(_void(), ListResultSchema), method(_void(), PreferredSchema), method(obje
23276
23536
  * to avoid mixed-content blocks in the browser. The public
23277
23537
  * tunnel always emits `https://` regardless. */
23278
23538
  scheme: _enum(["http", "https"]).optional()
23279
- }), GetConnectionEndpointsResultSchema), method(_void(), AllowedAddressesSchema), method(AllowedAddressesSchema, object({ success: literal(true) }), { kind: "mutation" }), method(_void(), AllowedAddressesSchema, { kind: "mutation" });
23539
+ }), GetConnectionEndpointsResultSchema), method(_void(), NotificationEndpointSchema), method(object({ baseUrl: string().nullable() }), NotificationEndpointSchema, { kind: "mutation" }), method(_void(), AllowedAddressesSchema), method(AllowedAddressesSchema, object({ success: literal(true) }), { kind: "mutation" }), method(_void(), AllowedAddressesSchema, { kind: "mutation" });
23280
23540
  /**
23281
23541
  * mesh-network — collection cap for mesh-VPN providers.
23282
23542
  *
@@ -24142,7 +24402,12 @@ var RecordingDeviceUsageSchema = object({
24142
24402
  var RecordingLocationUsageSchema = object({
24143
24403
  /** StorageLocation id; null for the legacy/degraded single-root fallback. */
24144
24404
  locationId: string().nullable(),
24145
- /** Bytes of recordings stored on this location. */
24405
+ /** Every location id sharing this row's PHYSICAL volume (aliases). One row
24406
+ * is emitted per physical disk (2026-07-29): two locations on one root
24407
+ * previously rendered as two identical "disks" with a nonsensical used
24408
+ * split — hydrate attribution across aliases is arbitrary by nature. */
24409
+ locationIds: array(string()).optional(),
24410
+ /** Bytes of recordings stored on this PHYSICAL volume (all aliases). */
24146
24411
  usedBytes: number(),
24147
24412
  /** Free bytes on the location's volume; null when capacity is unknown (remote). */
24148
24413
  availableBytes: number().nullable(),
@@ -24254,6 +24519,44 @@ method(object({
24254
24519
  }), method(OpsLogQueryInputSchema, array(OpsLogEntrySchema).readonly(), {
24255
24520
  kind: "query",
24256
24521
  auth: "admin"
24522
+ }), method(object({
24523
+ deviceId: number(),
24524
+ aroundMs: number(),
24525
+ preRollSec: number().min(0).max(30).default(2),
24526
+ postRollSec: number().min(0).max(30).default(5),
24527
+ maxWidth: number().int().min(120).max(1280).default(480),
24528
+ fps: number().int().min(1).max(15).default(5)
24529
+ }), object({
24530
+ gifBase64: string(),
24531
+ fromMs: number(),
24532
+ toMs: number()
24533
+ }), {
24534
+ kind: "mutation",
24535
+ auth: "admin"
24536
+ }), method(object({
24537
+ deviceId: number(),
24538
+ aroundMs: number(),
24539
+ preRollSec: number().min(0).max(30).default(3),
24540
+ postRollSec: number().min(0).max(30).default(7),
24541
+ maxWidth: number().int().min(160).max(1920).default(640)
24542
+ }), object({
24543
+ clipBase64: string(),
24544
+ mime: string(),
24545
+ fromMs: number(),
24546
+ toMs: number(),
24547
+ bytes: number().int()
24548
+ }), {
24549
+ kind: "mutation",
24550
+ auth: "admin"
24551
+ }), method(RelocateFootageInputSchema, object({ jobId: string() }), {
24552
+ kind: "mutation",
24553
+ auth: "admin"
24554
+ }), method(object({}), array(RelocateJobSchema).readonly(), {
24555
+ kind: "query",
24556
+ auth: "admin"
24557
+ }), method(object({ jobId: string() }), object({ cancelled: boolean() }), {
24558
+ kind: "mutation",
24559
+ auth: "admin"
24257
24560
  });
24258
24561
  /**
24259
24562
  * `recordingExport` cap — render a footage time range into a single downloadable
@@ -26056,6 +26359,12 @@ Object.freeze({
26056
26359
  addonId: null,
26057
26360
  access: "view"
26058
26361
  },
26362
+ "deviceManager.getDeviceStatusAggregateBatch": {
26363
+ capName: "device-manager",
26364
+ capScope: "system",
26365
+ addonId: null,
26366
+ access: "view"
26367
+ },
26059
26368
  "deviceManager.getLinkedDevices": {
26060
26369
  capName: "device-manager",
26061
26370
  capScope: "system",
@@ -26950,6 +27259,12 @@ Object.freeze({
26950
27259
  addonId: null,
26951
27260
  access: "view"
26952
27261
  },
27262
+ "localNetwork.getNotificationEndpoint": {
27263
+ capName: "local-network",
27264
+ capScope: "system",
27265
+ addonId: null,
27266
+ access: "view"
27267
+ },
26953
27268
  "localNetwork.getPreferred": {
26954
27269
  capName: "local-network",
26955
27270
  capScope: "system",
@@ -26974,6 +27289,12 @@ Object.freeze({
26974
27289
  addonId: null,
26975
27290
  access: "create"
26976
27291
  },
27292
+ "localNetwork.setNotificationEndpoint": {
27293
+ capName: "local-network",
27294
+ capScope: "system",
27295
+ addonId: null,
27296
+ access: "create"
27297
+ },
26977
27298
  "lockControl.lock": {
26978
27299
  capName: "lock-control",
26979
27300
  capScope: "device",
@@ -27616,6 +27937,12 @@ Object.freeze({
27616
27937
  addonId: null,
27617
27938
  access: "create"
27618
27939
  },
27940
+ "pipelineAnalytics.cancelMediaRelocate": {
27941
+ capName: "pipeline-analytics",
27942
+ capScope: "device",
27943
+ addonId: null,
27944
+ access: "create"
27945
+ },
27619
27946
  "pipelineAnalytics.clearTracks": {
27620
27947
  capName: "pipeline-analytics",
27621
27948
  capScope: "device",
@@ -27670,6 +27997,12 @@ Object.freeze({
27670
27997
  addonId: null,
27671
27998
  access: "view"
27672
27999
  },
28000
+ "pipelineAnalytics.getMediaRelocateStatus": {
28001
+ capName: "pipeline-analytics",
28002
+ capScope: "device",
28003
+ addonId: null,
28004
+ access: "view"
28005
+ },
27673
28006
  "pipelineAnalytics.getMotionEvents": {
27674
28007
  capName: "pipeline-analytics",
27675
28008
  capScope: "device",
@@ -27742,6 +28075,12 @@ Object.freeze({
27742
28075
  addonId: null,
27743
28076
  access: "create"
27744
28077
  },
28078
+ "pipelineAnalytics.relocateMedia": {
28079
+ capName: "pipeline-analytics",
28080
+ capScope: "device",
28081
+ addonId: null,
28082
+ access: "create"
28083
+ },
27745
28084
  "pipelineAnalytics.searchObjectEvents": {
27746
28085
  capName: "pipeline-analytics",
27747
28086
  capScope: "device",
@@ -28504,6 +28843,12 @@ Object.freeze({
28504
28843
  addonId: null,
28505
28844
  access: "create"
28506
28845
  },
28846
+ "recording.cancelRelocate": {
28847
+ capName: "recording",
28848
+ capScope: "system",
28849
+ addonId: null,
28850
+ access: "create"
28851
+ },
28507
28852
  "recording.deleteFootprint": {
28508
28853
  capName: "recording",
28509
28854
  capScope: "system",
@@ -28534,6 +28879,12 @@ Object.freeze({
28534
28879
  addonId: null,
28535
28880
  access: "view"
28536
28881
  },
28882
+ "recording.getRelocateStatus": {
28883
+ capName: "recording",
28884
+ capScope: "system",
28885
+ addonId: null,
28886
+ access: "view"
28887
+ },
28537
28888
  "recording.getStorageUsage": {
28538
28889
  capName: "recording",
28539
28890
  capScope: "system",
@@ -28564,6 +28915,24 @@ Object.freeze({
28564
28915
  addonId: null,
28565
28916
  access: "view"
28566
28917
  },
28918
+ "recording.relocateFootage": {
28919
+ capName: "recording",
28920
+ capScope: "system",
28921
+ addonId: null,
28922
+ access: "create"
28923
+ },
28924
+ "recording.renderClip": {
28925
+ capName: "recording",
28926
+ capScope: "system",
28927
+ addonId: null,
28928
+ access: "create"
28929
+ },
28930
+ "recording.renderGif": {
28931
+ capName: "recording",
28932
+ capScope: "system",
28933
+ addonId: null,
28934
+ access: "create"
28935
+ },
28567
28936
  "recording.rescanStorage": {
28568
28937
  capName: "recording",
28569
28938
  capScope: "system",
@@ -29158,6 +29527,12 @@ Object.freeze({
29158
29527
  addonId: null,
29159
29528
  access: "create"
29160
29529
  },
29530
+ "streamBroker.renderPreBufferClip": {
29531
+ capName: "stream-broker",
29532
+ capScope: "system",
29533
+ addonId: null,
29534
+ access: "create"
29535
+ },
29161
29536
  "streamBroker.restartProfile": {
29162
29537
  capName: "stream-broker",
29163
29538
  capScope: "system",