@camstack/addon-provider-unraid 0.1.10 → 0.1.12

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.
package/dist/addon.js CHANGED
@@ -1,5 +1,5 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
- //#region ../types/dist/event-category-H4AVePnn.mjs
2
+ //#region ../types/dist/event-category-D4HJq7Mw.mjs
3
3
  var EventCategory = /* @__PURE__ */ function(EventCategory) {
4
4
  EventCategory["SystemBoot"] = "system.boot";
5
5
  EventCategory["SystemAddonsReady"] = "system.addons-ready";
@@ -152,6 +152,11 @@ var EventCategory = /* @__PURE__ */ function(EventCategory) {
152
152
  /** Runner-sampled scrub thumbnail (~1/5 s/camera). Telemetry (D8): a lost
153
153
  * thumb is a scrub gap the recorder's keyframe backfill covers. */
154
154
  EventCategory["RecordingThumbSampled"] = "recording.thumb-sampled";
155
+ /** Export render progress (0–100). Telemetry (D8): a lost tick is a stale
156
+ * progress bar the client reconciles via `recordingExport.getExport`. */
157
+ EventCategory["RecordingExportProgress"] = "recording.export.progress";
158
+ EventCategory["RecordingExportCompleted"] = "recording.export.completed";
159
+ EventCategory["RecordingExportFailed"] = "recording.export.failed";
155
160
  EventCategory["DetectionEvent"] = "detection.event";
156
161
  EventCategory["SessionTrackNew"] = "session.track.new";
157
162
  EventCategory["SessionTrackExpired"] = "session.track.expired";
@@ -441,6 +446,25 @@ var EventCategory = /* @__PURE__ */ function(EventCategory) {
441
446
  */
442
447
  EventCategory["PipelineAnalyticsStationaryChanged"] = "pipeline-analytics.stationary-changed";
443
448
  /**
449
+ * Fired by `addon-post-analysis` when a package-drop is confirmed inside
450
+ * a package zone — a newly-appeared stationary object of a package class
451
+ * that cleared the class / zone / dwell / size gates. Payload:
452
+ * `PipelineAnalyticsPackageDeliveredPayload` carrying `{ deviceId,
453
+ * entryId, className, zoneIds, keyFrameMediaKey?, bbox, timestamp }`.
454
+ * Telemetry (D8): the durable record is the `package-events` store row;
455
+ * this bus topic drives notifier rules + live UI. See
456
+ * docs/superpowers/specs/2026-07-17-package-zones-design.md §5.1.
457
+ */
458
+ EventCategory["PipelineAnalyticsPackageDelivered"] = "pipeline-analytics.package-delivered";
459
+ /**
460
+ * Fired by `addon-post-analysis` when a previously-delivered package
461
+ * leaves its zone (the stationary entry departed — moved or swept).
462
+ * Payload: `PipelineAnalyticsPackagePickedUpPayload` carrying
463
+ * `{ deviceId, entryId, deliveredEventId, className, timestamp }`.
464
+ * Telemetry (D8). See package-zones-design §5.2.
465
+ */
466
+ EventCategory["PipelineAnalyticsPackagePickedUp"] = "pipeline-analytics.package-picked-up";
467
+ /**
444
468
  * Fired by `addon-post-analysis` whenever a gallery face row changes:
445
469
  * `kind:'buffered'` a new detected face was persisted, `'assigned'` /
446
470
  * `'unassigned'` its identity link changed, `'deleted'` the row was
@@ -5193,6 +5217,25 @@ function preprocess(fn, schema) {
5193
5217
  out: schema
5194
5218
  });
5195
5219
  }
5220
+ //#endregion
5221
+ //#region ../../node_modules/zod/v4/classic/compat.js
5222
+ /** @deprecated Use the raw string literal codes instead, e.g. "invalid_type". */
5223
+ var ZodIssueCode = {
5224
+ invalid_type: "invalid_type",
5225
+ too_big: "too_big",
5226
+ too_small: "too_small",
5227
+ invalid_format: "invalid_format",
5228
+ not_multiple_of: "not_multiple_of",
5229
+ unrecognized_keys: "unrecognized_keys",
5230
+ invalid_union: "invalid_union",
5231
+ invalid_key: "invalid_key",
5232
+ invalid_element: "invalid_element",
5233
+ invalid_value: "invalid_value",
5234
+ custom: "custom"
5235
+ };
5236
+ /** @deprecated Do not use. Stub definition, only included for zod-to-json-schema compatibility. */
5237
+ var ZodFirstPartyTypeKind;
5238
+ ZodFirstPartyTypeKind || (ZodFirstPartyTypeKind = {});
5196
5239
  Object.fromEntries([
5197
5240
  {
5198
5241
  id: "overview",
@@ -7251,6 +7294,24 @@ var RecordingRetentionSchema = object({
7251
7294
  maxSizeGb: number().min(0).optional()
7252
7295
  });
7253
7296
  /**
7297
+ * Scrub-thumbnail fidelity preset — the single per-camera selector bundling the
7298
+ * sprite tile RESOLUTION + JPEG QUALITY the recorder packs timeline-scrub
7299
+ * previews at. Five graduated steps; absent on a config = `standard` (the
7300
+ * shipped default, matching `sheet-geometry`/`sheet-composer`).
7301
+ *
7302
+ * Existing sheets are IMMUTABLE — a changed preset applies to NEW windows only.
7303
+ * Each window's index sidecar carries its own tile dims, so a camera whose
7304
+ * preset changed over time renders every historical window at the dims it was
7305
+ * written with.
7306
+ */
7307
+ var ScrubThumbnailPresetSchema = _enum([
7308
+ "minimal",
7309
+ "low",
7310
+ "standard",
7311
+ "high",
7312
+ "max"
7313
+ ]);
7314
+ /**
7254
7315
  * The full per-camera recording intent — the wire shape of a RecordingTarget.
7255
7316
  *
7256
7317
  * `mode` is the authoritative storage choice; `schedule`/`triggers`/`pre`/`post`
@@ -7287,7 +7348,14 @@ var RecordingConfigSchema = object({
7287
7348
  * derived into bands once via `migrateConfigToBands`.
7288
7349
  */
7289
7350
  bands: array(RecordingBandSchema).optional(),
7290
- retention: RecordingRetentionSchema.optional()
7351
+ retention: RecordingRetentionSchema.optional(),
7352
+ /**
7353
+ * Per-camera scrub-thumbnail fidelity preset (resolution + JPEG quality for
7354
+ * timeline-scrub sprite previews). Absent = `standard`. Applies to NEW
7355
+ * windows only — existing sheets are immutable, and each window's index
7356
+ * carries its own tile dims so mixed-preset history renders correctly.
7357
+ */
7358
+ scrubThumbnails: ScrubThumbnailPresetSchema.optional()
7291
7359
  });
7292
7360
  /**
7293
7361
  * `StorageLocationType` — an addon-declared id that identifies the *kind* of
@@ -14645,7 +14713,11 @@ array(ZoneRuleSchema).readonly();
14645
14713
  * Extend the enum here when a new gating consumer comes online (audio
14646
14714
  * gating, alert filtering, …) — no other surface needs to change.
14647
14715
  */
14648
- var ZoneRuleStageEnum = _enum(["motion", "detection"]);
14716
+ var ZoneRuleStageEnum = _enum([
14717
+ "motion",
14718
+ "detection",
14719
+ "package"
14720
+ ]);
14649
14721
  /**
14650
14722
  * Runtime registry: cap-property-name → cap definition. `BaseDevice`'s
14651
14723
  * `state` getter looks up the cap definition here to construct a
@@ -14739,16 +14811,24 @@ var DEVICE_LOCAL_STATE_CAPS = {
14739
14811
  })
14740
14812
  },
14741
14813
  /**
14742
- * Runtime-state slice — both stages mirrored together so consumers
14814
+ * Runtime-state slice — every stage mirrored together so consumers
14743
14815
  * see one reactive handle (`device.state.zoneRules.value`) instead
14744
- * of two. Bulk-replace mutations on either stage write the full
14745
- * `{motion, detection}` shape, so subscribers always get the
14816
+ * of one per stage. Bulk-replace mutations on any stage write the full
14817
+ * `{motion, detection, package}` shape, so subscribers always get the
14746
14818
  * complete current set. Consumers that only care about one stage
14747
14819
  * just read the matching property.
14820
+ *
14821
+ * `package` backs the package-drop detector — a package zone is a
14822
+ * `ZoneRule` on the `'package'` stage referencing drawn polygons
14823
+ * (see docs/superpowers/specs/2026-07-17-package-zones-design.md §3.1).
14824
+ * The orchestrator provider that writes this stage lands in a later
14825
+ * slice; until then the mirror carries only `{motion, detection}` and
14826
+ * consumers read `package` as absent (treat as `[]`).
14748
14827
  */
14749
14828
  runtimeState: object({
14750
14829
  motion: array(ZoneRuleSchema).readonly(),
14751
- detection: array(ZoneRuleSchema).readonly()
14830
+ detection: array(ZoneRuleSchema).readonly(),
14831
+ package: array(ZoneRuleSchema).readonly()
14752
14832
  })
14753
14833
  },
14754
14834
  zones: zonesCapability
@@ -18710,6 +18790,7 @@ var EventKindIconSchema = _enum([
18710
18790
  "smoke",
18711
18791
  "water",
18712
18792
  "button",
18793
+ "package",
18713
18794
  "generic"
18714
18795
  ]);
18715
18796
  var EventKindCategorySchema = _enum([
@@ -18717,7 +18798,8 @@ var EventKindCategorySchema = _enum([
18717
18798
  "audio",
18718
18799
  "detection",
18719
18800
  "sensor",
18720
- "custom"
18801
+ "custom",
18802
+ "package"
18721
18803
  ]);
18722
18804
  var EventKindDescriptorSchema = object({
18723
18805
  /** Stable kind id (e.g. 'motion', 'person', 'contact'). */
@@ -22380,6 +22462,108 @@ method(object({
22380
22462
  auth: "admin"
22381
22463
  });
22382
22464
  /**
22465
+ * `recordingExport` cap — render a footage time range into a single downloadable
22466
+ * MP4 (regular / accelerated / decelerated / timelapse, ± audio), kept for a
22467
+ * bounded lifetime with a durable history, auto-expiry, and optional
22468
+ * delete-after-download.
22469
+ *
22470
+ * Like `recording` this is a `scope:'system', mode:'singleton'` cap: the
22471
+ * recorder self-gates so exactly ONE node (the designated `recordingNodeId`,
22472
+ * default `hub`) registers it. Device-scoped methods carry `deviceId` in their
22473
+ * input and dispatch to that single provider; the render runs on the node that
22474
+ * owns the footage (no cross-node segment transfer). Download rides the
22475
+ * framework addon data-plane. State persists in a DurableState blob (NOT
22476
+ * SQLite); history rows survive file deletion for audit.
22477
+ */
22478
+ /** Playback-speed multiplier for the render (1 = realtime). */
22479
+ var ExportSpeedSchema = number().min(.25).max(32);
22480
+ /** Timelapse cadence — sample one source frame per `everyMs`, output at `outputFps`. */
22481
+ var ExportTimelapseSchema = object({
22482
+ everyMs: number().int().positive(),
22483
+ outputFps: number().int().min(1).max(60).optional()
22484
+ });
22485
+ /**
22486
+ * Render options. `speed` and `timelapse` are mutually exclusive. `includeAudio`
22487
+ * is honoured only for a realtime-ish speed (0.5–2×); timelapse is always
22488
+ * silent. `maxLifeMs` bounds how long the finished file is kept;
22489
+ * `deleteAfterDownload` removes it shortly after the first complete download.
22490
+ */
22491
+ var ExportOptionsSchema = object({
22492
+ speed: ExportSpeedSchema.optional(),
22493
+ timelapse: ExportTimelapseSchema.optional(),
22494
+ includeAudio: boolean(),
22495
+ maxLifeMs: number().int().positive(),
22496
+ deleteAfterDownload: boolean(),
22497
+ title: string().max(200).optional()
22498
+ }).superRefine((v, ctx) => {
22499
+ if (v.speed !== void 0 && v.timelapse !== void 0) ctx.addIssue({
22500
+ code: ZodIssueCode.custom,
22501
+ message: "speed and timelapse are mutually exclusive",
22502
+ path: ["timelapse"]
22503
+ });
22504
+ });
22505
+ var ExportStateSchema = _enum([
22506
+ "queued",
22507
+ "rendering",
22508
+ "ready",
22509
+ "failed",
22510
+ "expired",
22511
+ "deleted"
22512
+ ]);
22513
+ /** One export job / history row. */
22514
+ var ExportRecordSchema = object({
22515
+ id: string(),
22516
+ deviceId: number(),
22517
+ profile: string(),
22518
+ fromMs: number(),
22519
+ toMs: number(),
22520
+ options: ExportOptionsSchema,
22521
+ state: ExportStateSchema,
22522
+ /** 0–100 while rendering; null otherwise. */
22523
+ progressPct: number().nullable(),
22524
+ /** File size once ready; null before. */
22525
+ fileBytes: number().nullable(),
22526
+ expiresAt: number(),
22527
+ deleteAfterDownload: boolean(),
22528
+ /** Epoch of the first complete download; null until then. */
22529
+ downloadedAt: number().nullable(),
22530
+ createdAt: number(),
22531
+ /** User id/name that requested the export. */
22532
+ createdBy: string(),
22533
+ /** Failure reason when state is 'failed'; null otherwise. */
22534
+ error: string().nullable()
22535
+ });
22536
+ /** Candidate download URLs (LAN first, then operator extra hosts). */
22537
+ var ExportDownloadSchema = object({
22538
+ url: string(),
22539
+ endpoints: array(string())
22540
+ });
22541
+ method(object({
22542
+ deviceId: number(),
22543
+ profile: string(),
22544
+ fromMs: number(),
22545
+ toMs: number(),
22546
+ options: ExportOptionsSchema
22547
+ }), ExportRecordSchema, {
22548
+ kind: "mutation",
22549
+ auth: "protected"
22550
+ }), method(object({ deviceId: number().optional() }), array(ExportRecordSchema), {
22551
+ kind: "query",
22552
+ auth: "protected"
22553
+ }), method(object({ exportId: string() }), ExportRecordSchema, {
22554
+ kind: "query",
22555
+ auth: "protected"
22556
+ }), method(object({ exportId: string() }), ExportRecordSchema, {
22557
+ kind: "mutation",
22558
+ auth: "protected"
22559
+ }), method(object({ exportId: string() }), ExportRecordSchema, {
22560
+ kind: "mutation",
22561
+ auth: "protected"
22562
+ }), method(object({ exportId: string() }), ExportDownloadSchema, {
22563
+ kind: "query",
22564
+ auth: "protected"
22565
+ });
22566
+ /**
22383
22567
  * One publishable camera stream as its OWNING PROVIDER describes it — the same
22384
22568
  * payload the (legacy) push `streamBroker.publishCameraStream` carried, minus
22385
22569
  * `deviceId`.
@@ -26303,6 +26487,42 @@ Object.freeze({
26303
26487
  addonId: null,
26304
26488
  access: "create"
26305
26489
  },
26490
+ "recordingExport.cancelExport": {
26491
+ capName: "recordingExport",
26492
+ capScope: "system",
26493
+ addonId: null,
26494
+ access: "create"
26495
+ },
26496
+ "recordingExport.createExport": {
26497
+ capName: "recordingExport",
26498
+ capScope: "system",
26499
+ addonId: null,
26500
+ access: "create"
26501
+ },
26502
+ "recordingExport.deleteExport": {
26503
+ capName: "recordingExport",
26504
+ capScope: "system",
26505
+ addonId: null,
26506
+ access: "delete"
26507
+ },
26508
+ "recordingExport.getDownloadUrl": {
26509
+ capName: "recordingExport",
26510
+ capScope: "system",
26511
+ addonId: null,
26512
+ access: "view"
26513
+ },
26514
+ "recordingExport.getExport": {
26515
+ capName: "recordingExport",
26516
+ capScope: "system",
26517
+ addonId: null,
26518
+ access: "view"
26519
+ },
26520
+ "recordingExport.listExports": {
26521
+ capName: "recordingExport",
26522
+ capScope: "system",
26523
+ addonId: null,
26524
+ access: "view"
26525
+ },
26306
26526
  "sceneMonitor.captureReference": {
26307
26527
  capName: "scene-monitor",
26308
26528
  capScope: "device",
package/dist/addon.mjs CHANGED
@@ -1,4 +1,4 @@
1
- //#region ../types/dist/event-category-H4AVePnn.mjs
1
+ //#region ../types/dist/event-category-D4HJq7Mw.mjs
2
2
  var EventCategory = /* @__PURE__ */ function(EventCategory) {
3
3
  EventCategory["SystemBoot"] = "system.boot";
4
4
  EventCategory["SystemAddonsReady"] = "system.addons-ready";
@@ -151,6 +151,11 @@ var EventCategory = /* @__PURE__ */ function(EventCategory) {
151
151
  /** Runner-sampled scrub thumbnail (~1/5 s/camera). Telemetry (D8): a lost
152
152
  * thumb is a scrub gap the recorder's keyframe backfill covers. */
153
153
  EventCategory["RecordingThumbSampled"] = "recording.thumb-sampled";
154
+ /** Export render progress (0–100). Telemetry (D8): a lost tick is a stale
155
+ * progress bar the client reconciles via `recordingExport.getExport`. */
156
+ EventCategory["RecordingExportProgress"] = "recording.export.progress";
157
+ EventCategory["RecordingExportCompleted"] = "recording.export.completed";
158
+ EventCategory["RecordingExportFailed"] = "recording.export.failed";
154
159
  EventCategory["DetectionEvent"] = "detection.event";
155
160
  EventCategory["SessionTrackNew"] = "session.track.new";
156
161
  EventCategory["SessionTrackExpired"] = "session.track.expired";
@@ -440,6 +445,25 @@ var EventCategory = /* @__PURE__ */ function(EventCategory) {
440
445
  */
441
446
  EventCategory["PipelineAnalyticsStationaryChanged"] = "pipeline-analytics.stationary-changed";
442
447
  /**
448
+ * Fired by `addon-post-analysis` when a package-drop is confirmed inside
449
+ * a package zone — a newly-appeared stationary object of a package class
450
+ * that cleared the class / zone / dwell / size gates. Payload:
451
+ * `PipelineAnalyticsPackageDeliveredPayload` carrying `{ deviceId,
452
+ * entryId, className, zoneIds, keyFrameMediaKey?, bbox, timestamp }`.
453
+ * Telemetry (D8): the durable record is the `package-events` store row;
454
+ * this bus topic drives notifier rules + live UI. See
455
+ * docs/superpowers/specs/2026-07-17-package-zones-design.md §5.1.
456
+ */
457
+ EventCategory["PipelineAnalyticsPackageDelivered"] = "pipeline-analytics.package-delivered";
458
+ /**
459
+ * Fired by `addon-post-analysis` when a previously-delivered package
460
+ * leaves its zone (the stationary entry departed — moved or swept).
461
+ * Payload: `PipelineAnalyticsPackagePickedUpPayload` carrying
462
+ * `{ deviceId, entryId, deliveredEventId, className, timestamp }`.
463
+ * Telemetry (D8). See package-zones-design §5.2.
464
+ */
465
+ EventCategory["PipelineAnalyticsPackagePickedUp"] = "pipeline-analytics.package-picked-up";
466
+ /**
443
467
  * Fired by `addon-post-analysis` whenever a gallery face row changes:
444
468
  * `kind:'buffered'` a new detected face was persisted, `'assigned'` /
445
469
  * `'unassigned'` its identity link changed, `'deleted'` the row was
@@ -5192,6 +5216,25 @@ function preprocess(fn, schema) {
5192
5216
  out: schema
5193
5217
  });
5194
5218
  }
5219
+ //#endregion
5220
+ //#region ../../node_modules/zod/v4/classic/compat.js
5221
+ /** @deprecated Use the raw string literal codes instead, e.g. "invalid_type". */
5222
+ var ZodIssueCode = {
5223
+ invalid_type: "invalid_type",
5224
+ too_big: "too_big",
5225
+ too_small: "too_small",
5226
+ invalid_format: "invalid_format",
5227
+ not_multiple_of: "not_multiple_of",
5228
+ unrecognized_keys: "unrecognized_keys",
5229
+ invalid_union: "invalid_union",
5230
+ invalid_key: "invalid_key",
5231
+ invalid_element: "invalid_element",
5232
+ invalid_value: "invalid_value",
5233
+ custom: "custom"
5234
+ };
5235
+ /** @deprecated Do not use. Stub definition, only included for zod-to-json-schema compatibility. */
5236
+ var ZodFirstPartyTypeKind;
5237
+ ZodFirstPartyTypeKind || (ZodFirstPartyTypeKind = {});
5195
5238
  Object.fromEntries([
5196
5239
  {
5197
5240
  id: "overview",
@@ -7250,6 +7293,24 @@ var RecordingRetentionSchema = object({
7250
7293
  maxSizeGb: number().min(0).optional()
7251
7294
  });
7252
7295
  /**
7296
+ * Scrub-thumbnail fidelity preset — the single per-camera selector bundling the
7297
+ * sprite tile RESOLUTION + JPEG QUALITY the recorder packs timeline-scrub
7298
+ * previews at. Five graduated steps; absent on a config = `standard` (the
7299
+ * shipped default, matching `sheet-geometry`/`sheet-composer`).
7300
+ *
7301
+ * Existing sheets are IMMUTABLE — a changed preset applies to NEW windows only.
7302
+ * Each window's index sidecar carries its own tile dims, so a camera whose
7303
+ * preset changed over time renders every historical window at the dims it was
7304
+ * written with.
7305
+ */
7306
+ var ScrubThumbnailPresetSchema = _enum([
7307
+ "minimal",
7308
+ "low",
7309
+ "standard",
7310
+ "high",
7311
+ "max"
7312
+ ]);
7313
+ /**
7253
7314
  * The full per-camera recording intent — the wire shape of a RecordingTarget.
7254
7315
  *
7255
7316
  * `mode` is the authoritative storage choice; `schedule`/`triggers`/`pre`/`post`
@@ -7286,7 +7347,14 @@ var RecordingConfigSchema = object({
7286
7347
  * derived into bands once via `migrateConfigToBands`.
7287
7348
  */
7288
7349
  bands: array(RecordingBandSchema).optional(),
7289
- retention: RecordingRetentionSchema.optional()
7350
+ retention: RecordingRetentionSchema.optional(),
7351
+ /**
7352
+ * Per-camera scrub-thumbnail fidelity preset (resolution + JPEG quality for
7353
+ * timeline-scrub sprite previews). Absent = `standard`. Applies to NEW
7354
+ * windows only — existing sheets are immutable, and each window's index
7355
+ * carries its own tile dims so mixed-preset history renders correctly.
7356
+ */
7357
+ scrubThumbnails: ScrubThumbnailPresetSchema.optional()
7290
7358
  });
7291
7359
  /**
7292
7360
  * `StorageLocationType` — an addon-declared id that identifies the *kind* of
@@ -14644,7 +14712,11 @@ array(ZoneRuleSchema).readonly();
14644
14712
  * Extend the enum here when a new gating consumer comes online (audio
14645
14713
  * gating, alert filtering, …) — no other surface needs to change.
14646
14714
  */
14647
- var ZoneRuleStageEnum = _enum(["motion", "detection"]);
14715
+ var ZoneRuleStageEnum = _enum([
14716
+ "motion",
14717
+ "detection",
14718
+ "package"
14719
+ ]);
14648
14720
  /**
14649
14721
  * Runtime registry: cap-property-name → cap definition. `BaseDevice`'s
14650
14722
  * `state` getter looks up the cap definition here to construct a
@@ -14738,16 +14810,24 @@ var DEVICE_LOCAL_STATE_CAPS = {
14738
14810
  })
14739
14811
  },
14740
14812
  /**
14741
- * Runtime-state slice — both stages mirrored together so consumers
14813
+ * Runtime-state slice — every stage mirrored together so consumers
14742
14814
  * see one reactive handle (`device.state.zoneRules.value`) instead
14743
- * of two. Bulk-replace mutations on either stage write the full
14744
- * `{motion, detection}` shape, so subscribers always get the
14815
+ * of one per stage. Bulk-replace mutations on any stage write the full
14816
+ * `{motion, detection, package}` shape, so subscribers always get the
14745
14817
  * complete current set. Consumers that only care about one stage
14746
14818
  * just read the matching property.
14819
+ *
14820
+ * `package` backs the package-drop detector — a package zone is a
14821
+ * `ZoneRule` on the `'package'` stage referencing drawn polygons
14822
+ * (see docs/superpowers/specs/2026-07-17-package-zones-design.md §3.1).
14823
+ * The orchestrator provider that writes this stage lands in a later
14824
+ * slice; until then the mirror carries only `{motion, detection}` and
14825
+ * consumers read `package` as absent (treat as `[]`).
14747
14826
  */
14748
14827
  runtimeState: object({
14749
14828
  motion: array(ZoneRuleSchema).readonly(),
14750
- detection: array(ZoneRuleSchema).readonly()
14829
+ detection: array(ZoneRuleSchema).readonly(),
14830
+ package: array(ZoneRuleSchema).readonly()
14751
14831
  })
14752
14832
  },
14753
14833
  zones: zonesCapability
@@ -18709,6 +18789,7 @@ var EventKindIconSchema = _enum([
18709
18789
  "smoke",
18710
18790
  "water",
18711
18791
  "button",
18792
+ "package",
18712
18793
  "generic"
18713
18794
  ]);
18714
18795
  var EventKindCategorySchema = _enum([
@@ -18716,7 +18797,8 @@ var EventKindCategorySchema = _enum([
18716
18797
  "audio",
18717
18798
  "detection",
18718
18799
  "sensor",
18719
- "custom"
18800
+ "custom",
18801
+ "package"
18720
18802
  ]);
18721
18803
  var EventKindDescriptorSchema = object({
18722
18804
  /** Stable kind id (e.g. 'motion', 'person', 'contact'). */
@@ -22379,6 +22461,108 @@ method(object({
22379
22461
  auth: "admin"
22380
22462
  });
22381
22463
  /**
22464
+ * `recordingExport` cap — render a footage time range into a single downloadable
22465
+ * MP4 (regular / accelerated / decelerated / timelapse, ± audio), kept for a
22466
+ * bounded lifetime with a durable history, auto-expiry, and optional
22467
+ * delete-after-download.
22468
+ *
22469
+ * Like `recording` this is a `scope:'system', mode:'singleton'` cap: the
22470
+ * recorder self-gates so exactly ONE node (the designated `recordingNodeId`,
22471
+ * default `hub`) registers it. Device-scoped methods carry `deviceId` in their
22472
+ * input and dispatch to that single provider; the render runs on the node that
22473
+ * owns the footage (no cross-node segment transfer). Download rides the
22474
+ * framework addon data-plane. State persists in a DurableState blob (NOT
22475
+ * SQLite); history rows survive file deletion for audit.
22476
+ */
22477
+ /** Playback-speed multiplier for the render (1 = realtime). */
22478
+ var ExportSpeedSchema = number().min(.25).max(32);
22479
+ /** Timelapse cadence — sample one source frame per `everyMs`, output at `outputFps`. */
22480
+ var ExportTimelapseSchema = object({
22481
+ everyMs: number().int().positive(),
22482
+ outputFps: number().int().min(1).max(60).optional()
22483
+ });
22484
+ /**
22485
+ * Render options. `speed` and `timelapse` are mutually exclusive. `includeAudio`
22486
+ * is honoured only for a realtime-ish speed (0.5–2×); timelapse is always
22487
+ * silent. `maxLifeMs` bounds how long the finished file is kept;
22488
+ * `deleteAfterDownload` removes it shortly after the first complete download.
22489
+ */
22490
+ var ExportOptionsSchema = object({
22491
+ speed: ExportSpeedSchema.optional(),
22492
+ timelapse: ExportTimelapseSchema.optional(),
22493
+ includeAudio: boolean(),
22494
+ maxLifeMs: number().int().positive(),
22495
+ deleteAfterDownload: boolean(),
22496
+ title: string().max(200).optional()
22497
+ }).superRefine((v, ctx) => {
22498
+ if (v.speed !== void 0 && v.timelapse !== void 0) ctx.addIssue({
22499
+ code: ZodIssueCode.custom,
22500
+ message: "speed and timelapse are mutually exclusive",
22501
+ path: ["timelapse"]
22502
+ });
22503
+ });
22504
+ var ExportStateSchema = _enum([
22505
+ "queued",
22506
+ "rendering",
22507
+ "ready",
22508
+ "failed",
22509
+ "expired",
22510
+ "deleted"
22511
+ ]);
22512
+ /** One export job / history row. */
22513
+ var ExportRecordSchema = object({
22514
+ id: string(),
22515
+ deviceId: number(),
22516
+ profile: string(),
22517
+ fromMs: number(),
22518
+ toMs: number(),
22519
+ options: ExportOptionsSchema,
22520
+ state: ExportStateSchema,
22521
+ /** 0–100 while rendering; null otherwise. */
22522
+ progressPct: number().nullable(),
22523
+ /** File size once ready; null before. */
22524
+ fileBytes: number().nullable(),
22525
+ expiresAt: number(),
22526
+ deleteAfterDownload: boolean(),
22527
+ /** Epoch of the first complete download; null until then. */
22528
+ downloadedAt: number().nullable(),
22529
+ createdAt: number(),
22530
+ /** User id/name that requested the export. */
22531
+ createdBy: string(),
22532
+ /** Failure reason when state is 'failed'; null otherwise. */
22533
+ error: string().nullable()
22534
+ });
22535
+ /** Candidate download URLs (LAN first, then operator extra hosts). */
22536
+ var ExportDownloadSchema = object({
22537
+ url: string(),
22538
+ endpoints: array(string())
22539
+ });
22540
+ method(object({
22541
+ deviceId: number(),
22542
+ profile: string(),
22543
+ fromMs: number(),
22544
+ toMs: number(),
22545
+ options: ExportOptionsSchema
22546
+ }), ExportRecordSchema, {
22547
+ kind: "mutation",
22548
+ auth: "protected"
22549
+ }), method(object({ deviceId: number().optional() }), array(ExportRecordSchema), {
22550
+ kind: "query",
22551
+ auth: "protected"
22552
+ }), method(object({ exportId: string() }), ExportRecordSchema, {
22553
+ kind: "query",
22554
+ auth: "protected"
22555
+ }), method(object({ exportId: string() }), ExportRecordSchema, {
22556
+ kind: "mutation",
22557
+ auth: "protected"
22558
+ }), method(object({ exportId: string() }), ExportRecordSchema, {
22559
+ kind: "mutation",
22560
+ auth: "protected"
22561
+ }), method(object({ exportId: string() }), ExportDownloadSchema, {
22562
+ kind: "query",
22563
+ auth: "protected"
22564
+ });
22565
+ /**
22382
22566
  * One publishable camera stream as its OWNING PROVIDER describes it — the same
22383
22567
  * payload the (legacy) push `streamBroker.publishCameraStream` carried, minus
22384
22568
  * `deviceId`.
@@ -26302,6 +26486,42 @@ Object.freeze({
26302
26486
  addonId: null,
26303
26487
  access: "create"
26304
26488
  },
26489
+ "recordingExport.cancelExport": {
26490
+ capName: "recordingExport",
26491
+ capScope: "system",
26492
+ addonId: null,
26493
+ access: "create"
26494
+ },
26495
+ "recordingExport.createExport": {
26496
+ capName: "recordingExport",
26497
+ capScope: "system",
26498
+ addonId: null,
26499
+ access: "create"
26500
+ },
26501
+ "recordingExport.deleteExport": {
26502
+ capName: "recordingExport",
26503
+ capScope: "system",
26504
+ addonId: null,
26505
+ access: "delete"
26506
+ },
26507
+ "recordingExport.getDownloadUrl": {
26508
+ capName: "recordingExport",
26509
+ capScope: "system",
26510
+ addonId: null,
26511
+ access: "view"
26512
+ },
26513
+ "recordingExport.getExport": {
26514
+ capName: "recordingExport",
26515
+ capScope: "system",
26516
+ addonId: null,
26517
+ access: "view"
26518
+ },
26519
+ "recordingExport.listExports": {
26520
+ capName: "recordingExport",
26521
+ capScope: "system",
26522
+ addonId: null,
26523
+ access: "view"
26524
+ },
26305
26525
  "sceneMonitor.captureReference": {
26306
26526
  capName: "scene-monitor",
26307
26527
  capScope: "device",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@camstack/addon-provider-unraid",
3
- "version": "0.1.10",
3
+ "version": "0.1.12",
4
4
  "description": "Unraid device-provider addon for CamStack — one Container per Unraid instance fanning out array, disk, docker and notification entities",
5
5
  "keywords": [
6
6
  "camstack",