@camstack/addon-provider-homematic 1.1.23 → 1.1.25

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
@@ -8,7 +8,7 @@ let http = require("http");
8
8
  let crypto$1 = require("crypto");
9
9
  let fs_promises = require("fs/promises");
10
10
  let path = require("path");
11
- //#region ../types/dist/event-category-H4AVePnn.mjs
11
+ //#region ../types/dist/event-category-D4HJq7Mw.mjs
12
12
  var EventCategory = /* @__PURE__ */ function(EventCategory) {
13
13
  EventCategory["SystemBoot"] = "system.boot";
14
14
  EventCategory["SystemAddonsReady"] = "system.addons-ready";
@@ -161,6 +161,11 @@ var EventCategory = /* @__PURE__ */ function(EventCategory) {
161
161
  /** Runner-sampled scrub thumbnail (~1/5 s/camera). Telemetry (D8): a lost
162
162
  * thumb is a scrub gap the recorder's keyframe backfill covers. */
163
163
  EventCategory["RecordingThumbSampled"] = "recording.thumb-sampled";
164
+ /** Export render progress (0–100). Telemetry (D8): a lost tick is a stale
165
+ * progress bar the client reconciles via `recordingExport.getExport`. */
166
+ EventCategory["RecordingExportProgress"] = "recording.export.progress";
167
+ EventCategory["RecordingExportCompleted"] = "recording.export.completed";
168
+ EventCategory["RecordingExportFailed"] = "recording.export.failed";
164
169
  EventCategory["DetectionEvent"] = "detection.event";
165
170
  EventCategory["SessionTrackNew"] = "session.track.new";
166
171
  EventCategory["SessionTrackExpired"] = "session.track.expired";
@@ -450,6 +455,25 @@ var EventCategory = /* @__PURE__ */ function(EventCategory) {
450
455
  */
451
456
  EventCategory["PipelineAnalyticsStationaryChanged"] = "pipeline-analytics.stationary-changed";
452
457
  /**
458
+ * Fired by `addon-post-analysis` when a package-drop is confirmed inside
459
+ * a package zone — a newly-appeared stationary object of a package class
460
+ * that cleared the class / zone / dwell / size gates. Payload:
461
+ * `PipelineAnalyticsPackageDeliveredPayload` carrying `{ deviceId,
462
+ * entryId, className, zoneIds, keyFrameMediaKey?, bbox, timestamp }`.
463
+ * Telemetry (D8): the durable record is the `package-events` store row;
464
+ * this bus topic drives notifier rules + live UI. See
465
+ * docs/superpowers/specs/2026-07-17-package-zones-design.md §5.1.
466
+ */
467
+ EventCategory["PipelineAnalyticsPackageDelivered"] = "pipeline-analytics.package-delivered";
468
+ /**
469
+ * Fired by `addon-post-analysis` when a previously-delivered package
470
+ * leaves its zone (the stationary entry departed — moved or swept).
471
+ * Payload: `PipelineAnalyticsPackagePickedUpPayload` carrying
472
+ * `{ deviceId, entryId, deliveredEventId, className, timestamp }`.
473
+ * Telemetry (D8). See package-zones-design §5.2.
474
+ */
475
+ EventCategory["PipelineAnalyticsPackagePickedUp"] = "pipeline-analytics.package-picked-up";
476
+ /**
453
477
  * Fired by `addon-post-analysis` whenever a gallery face row changes:
454
478
  * `kind:'buffered'` a new detected face was persisted, `'assigned'` /
455
479
  * `'unassigned'` its identity link changed, `'deleted'` the row was
@@ -5202,6 +5226,25 @@ function preprocess(fn, schema) {
5202
5226
  out: schema
5203
5227
  });
5204
5228
  }
5229
+ //#endregion
5230
+ //#region ../../node_modules/zod/v4/classic/compat.js
5231
+ /** @deprecated Use the raw string literal codes instead, e.g. "invalid_type". */
5232
+ var ZodIssueCode = {
5233
+ invalid_type: "invalid_type",
5234
+ too_big: "too_big",
5235
+ too_small: "too_small",
5236
+ invalid_format: "invalid_format",
5237
+ not_multiple_of: "not_multiple_of",
5238
+ unrecognized_keys: "unrecognized_keys",
5239
+ invalid_union: "invalid_union",
5240
+ invalid_key: "invalid_key",
5241
+ invalid_element: "invalid_element",
5242
+ invalid_value: "invalid_value",
5243
+ custom: "custom"
5244
+ };
5245
+ /** @deprecated Do not use. Stub definition, only included for zod-to-json-schema compatibility. */
5246
+ var ZodFirstPartyTypeKind;
5247
+ ZodFirstPartyTypeKind || (ZodFirstPartyTypeKind = {});
5205
5248
  Object.fromEntries([
5206
5249
  {
5207
5250
  id: "overview",
@@ -7260,6 +7303,24 @@ var RecordingRetentionSchema = object({
7260
7303
  maxSizeGb: number().min(0).optional()
7261
7304
  });
7262
7305
  /**
7306
+ * Scrub-thumbnail fidelity preset — the single per-camera selector bundling the
7307
+ * sprite tile RESOLUTION + JPEG QUALITY the recorder packs timeline-scrub
7308
+ * previews at. Five graduated steps; absent on a config = `standard` (the
7309
+ * shipped default, matching `sheet-geometry`/`sheet-composer`).
7310
+ *
7311
+ * Existing sheets are IMMUTABLE — a changed preset applies to NEW windows only.
7312
+ * Each window's index sidecar carries its own tile dims, so a camera whose
7313
+ * preset changed over time renders every historical window at the dims it was
7314
+ * written with.
7315
+ */
7316
+ var ScrubThumbnailPresetSchema = _enum([
7317
+ "minimal",
7318
+ "low",
7319
+ "standard",
7320
+ "high",
7321
+ "max"
7322
+ ]);
7323
+ /**
7263
7324
  * The full per-camera recording intent — the wire shape of a RecordingTarget.
7264
7325
  *
7265
7326
  * `mode` is the authoritative storage choice; `schedule`/`triggers`/`pre`/`post`
@@ -7296,7 +7357,14 @@ var RecordingConfigSchema = object({
7296
7357
  * derived into bands once via `migrateConfigToBands`.
7297
7358
  */
7298
7359
  bands: array(RecordingBandSchema).optional(),
7299
- retention: RecordingRetentionSchema.optional()
7360
+ retention: RecordingRetentionSchema.optional(),
7361
+ /**
7362
+ * Per-camera scrub-thumbnail fidelity preset (resolution + JPEG quality for
7363
+ * timeline-scrub sprite previews). Absent = `standard`. Applies to NEW
7364
+ * windows only — existing sheets are immutable, and each window's index
7365
+ * carries its own tile dims so mixed-preset history renders correctly.
7366
+ */
7367
+ scrubThumbnails: ScrubThumbnailPresetSchema.optional()
7300
7368
  });
7301
7369
  /**
7302
7370
  * `StorageLocationType` — an addon-declared id that identifies the *kind* of
@@ -14654,7 +14722,11 @@ array(ZoneRuleSchema).readonly();
14654
14722
  * Extend the enum here when a new gating consumer comes online (audio
14655
14723
  * gating, alert filtering, …) — no other surface needs to change.
14656
14724
  */
14657
- var ZoneRuleStageEnum = _enum(["motion", "detection"]);
14725
+ var ZoneRuleStageEnum = _enum([
14726
+ "motion",
14727
+ "detection",
14728
+ "package"
14729
+ ]);
14658
14730
  /**
14659
14731
  * Runtime registry: cap-property-name → cap definition. `BaseDevice`'s
14660
14732
  * `state` getter looks up the cap definition here to construct a
@@ -14748,16 +14820,24 @@ var DEVICE_LOCAL_STATE_CAPS = {
14748
14820
  })
14749
14821
  },
14750
14822
  /**
14751
- * Runtime-state slice — both stages mirrored together so consumers
14823
+ * Runtime-state slice — every stage mirrored together so consumers
14752
14824
  * see one reactive handle (`device.state.zoneRules.value`) instead
14753
- * of two. Bulk-replace mutations on either stage write the full
14754
- * `{motion, detection}` shape, so subscribers always get the
14825
+ * of one per stage. Bulk-replace mutations on any stage write the full
14826
+ * `{motion, detection, package}` shape, so subscribers always get the
14755
14827
  * complete current set. Consumers that only care about one stage
14756
14828
  * just read the matching property.
14829
+ *
14830
+ * `package` backs the package-drop detector — a package zone is a
14831
+ * `ZoneRule` on the `'package'` stage referencing drawn polygons
14832
+ * (see docs/superpowers/specs/2026-07-17-package-zones-design.md §3.1).
14833
+ * The orchestrator provider that writes this stage lands in a later
14834
+ * slice; until then the mirror carries only `{motion, detection}` and
14835
+ * consumers read `package` as absent (treat as `[]`).
14757
14836
  */
14758
14837
  runtimeState: object({
14759
14838
  motion: array(ZoneRuleSchema).readonly(),
14760
- detection: array(ZoneRuleSchema).readonly()
14839
+ detection: array(ZoneRuleSchema).readonly(),
14840
+ package: array(ZoneRuleSchema).readonly()
14761
14841
  })
14762
14842
  },
14763
14843
  zones: zonesCapability
@@ -18785,6 +18865,7 @@ var EventKindIconSchema = _enum([
18785
18865
  "smoke",
18786
18866
  "water",
18787
18867
  "button",
18868
+ "package",
18788
18869
  "generic"
18789
18870
  ]);
18790
18871
  var EventKindCategorySchema = _enum([
@@ -18792,7 +18873,8 @@ var EventKindCategorySchema = _enum([
18792
18873
  "audio",
18793
18874
  "detection",
18794
18875
  "sensor",
18795
- "custom"
18876
+ "custom",
18877
+ "package"
18796
18878
  ]);
18797
18879
  var EventKindDescriptorSchema = object({
18798
18880
  /** Stable kind id (e.g. 'motion', 'person', 'contact'). */
@@ -22438,6 +22520,108 @@ method(object({
22438
22520
  auth: "admin"
22439
22521
  });
22440
22522
  /**
22523
+ * `recordingExport` cap — render a footage time range into a single downloadable
22524
+ * MP4 (regular / accelerated / decelerated / timelapse, ± audio), kept for a
22525
+ * bounded lifetime with a durable history, auto-expiry, and optional
22526
+ * delete-after-download.
22527
+ *
22528
+ * Like `recording` this is a `scope:'system', mode:'singleton'` cap: the
22529
+ * recorder self-gates so exactly ONE node (the designated `recordingNodeId`,
22530
+ * default `hub`) registers it. Device-scoped methods carry `deviceId` in their
22531
+ * input and dispatch to that single provider; the render runs on the node that
22532
+ * owns the footage (no cross-node segment transfer). Download rides the
22533
+ * framework addon data-plane. State persists in a DurableState blob (NOT
22534
+ * SQLite); history rows survive file deletion for audit.
22535
+ */
22536
+ /** Playback-speed multiplier for the render (1 = realtime). */
22537
+ var ExportSpeedSchema = number().min(.25).max(32);
22538
+ /** Timelapse cadence — sample one source frame per `everyMs`, output at `outputFps`. */
22539
+ var ExportTimelapseSchema = object({
22540
+ everyMs: number().int().positive(),
22541
+ outputFps: number().int().min(1).max(60).optional()
22542
+ });
22543
+ /**
22544
+ * Render options. `speed` and `timelapse` are mutually exclusive. `includeAudio`
22545
+ * is honoured only for a realtime-ish speed (0.5–2×); timelapse is always
22546
+ * silent. `maxLifeMs` bounds how long the finished file is kept;
22547
+ * `deleteAfterDownload` removes it shortly after the first complete download.
22548
+ */
22549
+ var ExportOptionsSchema = object({
22550
+ speed: ExportSpeedSchema.optional(),
22551
+ timelapse: ExportTimelapseSchema.optional(),
22552
+ includeAudio: boolean(),
22553
+ maxLifeMs: number().int().positive(),
22554
+ deleteAfterDownload: boolean(),
22555
+ title: string().max(200).optional()
22556
+ }).superRefine((v, ctx) => {
22557
+ if (v.speed !== void 0 && v.timelapse !== void 0) ctx.addIssue({
22558
+ code: ZodIssueCode.custom,
22559
+ message: "speed and timelapse are mutually exclusive",
22560
+ path: ["timelapse"]
22561
+ });
22562
+ });
22563
+ var ExportStateSchema = _enum([
22564
+ "queued",
22565
+ "rendering",
22566
+ "ready",
22567
+ "failed",
22568
+ "expired",
22569
+ "deleted"
22570
+ ]);
22571
+ /** One export job / history row. */
22572
+ var ExportRecordSchema = object({
22573
+ id: string(),
22574
+ deviceId: number(),
22575
+ profile: string(),
22576
+ fromMs: number(),
22577
+ toMs: number(),
22578
+ options: ExportOptionsSchema,
22579
+ state: ExportStateSchema,
22580
+ /** 0–100 while rendering; null otherwise. */
22581
+ progressPct: number().nullable(),
22582
+ /** File size once ready; null before. */
22583
+ fileBytes: number().nullable(),
22584
+ expiresAt: number(),
22585
+ deleteAfterDownload: boolean(),
22586
+ /** Epoch of the first complete download; null until then. */
22587
+ downloadedAt: number().nullable(),
22588
+ createdAt: number(),
22589
+ /** User id/name that requested the export. */
22590
+ createdBy: string(),
22591
+ /** Failure reason when state is 'failed'; null otherwise. */
22592
+ error: string().nullable()
22593
+ });
22594
+ /** Candidate download URLs (LAN first, then operator extra hosts). */
22595
+ var ExportDownloadSchema = object({
22596
+ url: string(),
22597
+ endpoints: array(string())
22598
+ });
22599
+ method(object({
22600
+ deviceId: number(),
22601
+ profile: string(),
22602
+ fromMs: number(),
22603
+ toMs: number(),
22604
+ options: ExportOptionsSchema
22605
+ }), ExportRecordSchema, {
22606
+ kind: "mutation",
22607
+ auth: "protected"
22608
+ }), method(object({ deviceId: number().optional() }), array(ExportRecordSchema), {
22609
+ kind: "query",
22610
+ auth: "protected"
22611
+ }), method(object({ exportId: string() }), ExportRecordSchema, {
22612
+ kind: "query",
22613
+ auth: "protected"
22614
+ }), method(object({ exportId: string() }), ExportRecordSchema, {
22615
+ kind: "mutation",
22616
+ auth: "protected"
22617
+ }), method(object({ exportId: string() }), ExportRecordSchema, {
22618
+ kind: "mutation",
22619
+ auth: "protected"
22620
+ }), method(object({ exportId: string() }), ExportDownloadSchema, {
22621
+ kind: "query",
22622
+ auth: "protected"
22623
+ });
22624
+ /**
22441
22625
  * One publishable camera stream as its OWNING PROVIDER describes it — the same
22442
22626
  * payload the (legacy) push `streamBroker.publishCameraStream` carried, minus
22443
22627
  * `deviceId`.
@@ -26361,6 +26545,42 @@ Object.freeze({
26361
26545
  addonId: null,
26362
26546
  access: "create"
26363
26547
  },
26548
+ "recordingExport.cancelExport": {
26549
+ capName: "recordingExport",
26550
+ capScope: "system",
26551
+ addonId: null,
26552
+ access: "create"
26553
+ },
26554
+ "recordingExport.createExport": {
26555
+ capName: "recordingExport",
26556
+ capScope: "system",
26557
+ addonId: null,
26558
+ access: "create"
26559
+ },
26560
+ "recordingExport.deleteExport": {
26561
+ capName: "recordingExport",
26562
+ capScope: "system",
26563
+ addonId: null,
26564
+ access: "delete"
26565
+ },
26566
+ "recordingExport.getDownloadUrl": {
26567
+ capName: "recordingExport",
26568
+ capScope: "system",
26569
+ addonId: null,
26570
+ access: "view"
26571
+ },
26572
+ "recordingExport.getExport": {
26573
+ capName: "recordingExport",
26574
+ capScope: "system",
26575
+ addonId: null,
26576
+ access: "view"
26577
+ },
26578
+ "recordingExport.listExports": {
26579
+ capName: "recordingExport",
26580
+ capScope: "system",
26581
+ addonId: null,
26582
+ access: "view"
26583
+ },
26364
26584
  "sceneMonitor.captureReference": {
26365
26585
  capName: "scene-monitor",
26366
26586
  capScope: "device",
package/dist/addon.mjs CHANGED
@@ -9,7 +9,7 @@ import { join } from "path";
9
9
  var __commonJSMin = (cb, mod) => () => (mod || (cb((mod = { exports: {} }).exports, mod), cb = null), mod.exports);
10
10
  var __require = /* @__PURE__ */ createRequire(import.meta.url);
11
11
  //#endregion
12
- //#region ../types/dist/event-category-H4AVePnn.mjs
12
+ //#region ../types/dist/event-category-D4HJq7Mw.mjs
13
13
  var EventCategory = /* @__PURE__ */ function(EventCategory) {
14
14
  EventCategory["SystemBoot"] = "system.boot";
15
15
  EventCategory["SystemAddonsReady"] = "system.addons-ready";
@@ -162,6 +162,11 @@ var EventCategory = /* @__PURE__ */ function(EventCategory) {
162
162
  /** Runner-sampled scrub thumbnail (~1/5 s/camera). Telemetry (D8): a lost
163
163
  * thumb is a scrub gap the recorder's keyframe backfill covers. */
164
164
  EventCategory["RecordingThumbSampled"] = "recording.thumb-sampled";
165
+ /** Export render progress (0–100). Telemetry (D8): a lost tick is a stale
166
+ * progress bar the client reconciles via `recordingExport.getExport`. */
167
+ EventCategory["RecordingExportProgress"] = "recording.export.progress";
168
+ EventCategory["RecordingExportCompleted"] = "recording.export.completed";
169
+ EventCategory["RecordingExportFailed"] = "recording.export.failed";
165
170
  EventCategory["DetectionEvent"] = "detection.event";
166
171
  EventCategory["SessionTrackNew"] = "session.track.new";
167
172
  EventCategory["SessionTrackExpired"] = "session.track.expired";
@@ -451,6 +456,25 @@ var EventCategory = /* @__PURE__ */ function(EventCategory) {
451
456
  */
452
457
  EventCategory["PipelineAnalyticsStationaryChanged"] = "pipeline-analytics.stationary-changed";
453
458
  /**
459
+ * Fired by `addon-post-analysis` when a package-drop is confirmed inside
460
+ * a package zone — a newly-appeared stationary object of a package class
461
+ * that cleared the class / zone / dwell / size gates. Payload:
462
+ * `PipelineAnalyticsPackageDeliveredPayload` carrying `{ deviceId,
463
+ * entryId, className, zoneIds, keyFrameMediaKey?, bbox, timestamp }`.
464
+ * Telemetry (D8): the durable record is the `package-events` store row;
465
+ * this bus topic drives notifier rules + live UI. See
466
+ * docs/superpowers/specs/2026-07-17-package-zones-design.md §5.1.
467
+ */
468
+ EventCategory["PipelineAnalyticsPackageDelivered"] = "pipeline-analytics.package-delivered";
469
+ /**
470
+ * Fired by `addon-post-analysis` when a previously-delivered package
471
+ * leaves its zone (the stationary entry departed — moved or swept).
472
+ * Payload: `PipelineAnalyticsPackagePickedUpPayload` carrying
473
+ * `{ deviceId, entryId, deliveredEventId, className, timestamp }`.
474
+ * Telemetry (D8). See package-zones-design §5.2.
475
+ */
476
+ EventCategory["PipelineAnalyticsPackagePickedUp"] = "pipeline-analytics.package-picked-up";
477
+ /**
454
478
  * Fired by `addon-post-analysis` whenever a gallery face row changes:
455
479
  * `kind:'buffered'` a new detected face was persisted, `'assigned'` /
456
480
  * `'unassigned'` its identity link changed, `'deleted'` the row was
@@ -5203,6 +5227,25 @@ function preprocess(fn, schema) {
5203
5227
  out: schema
5204
5228
  });
5205
5229
  }
5230
+ //#endregion
5231
+ //#region ../../node_modules/zod/v4/classic/compat.js
5232
+ /** @deprecated Use the raw string literal codes instead, e.g. "invalid_type". */
5233
+ var ZodIssueCode = {
5234
+ invalid_type: "invalid_type",
5235
+ too_big: "too_big",
5236
+ too_small: "too_small",
5237
+ invalid_format: "invalid_format",
5238
+ not_multiple_of: "not_multiple_of",
5239
+ unrecognized_keys: "unrecognized_keys",
5240
+ invalid_union: "invalid_union",
5241
+ invalid_key: "invalid_key",
5242
+ invalid_element: "invalid_element",
5243
+ invalid_value: "invalid_value",
5244
+ custom: "custom"
5245
+ };
5246
+ /** @deprecated Do not use. Stub definition, only included for zod-to-json-schema compatibility. */
5247
+ var ZodFirstPartyTypeKind;
5248
+ ZodFirstPartyTypeKind || (ZodFirstPartyTypeKind = {});
5206
5249
  Object.fromEntries([
5207
5250
  {
5208
5251
  id: "overview",
@@ -7261,6 +7304,24 @@ var RecordingRetentionSchema = object({
7261
7304
  maxSizeGb: number().min(0).optional()
7262
7305
  });
7263
7306
  /**
7307
+ * Scrub-thumbnail fidelity preset — the single per-camera selector bundling the
7308
+ * sprite tile RESOLUTION + JPEG QUALITY the recorder packs timeline-scrub
7309
+ * previews at. Five graduated steps; absent on a config = `standard` (the
7310
+ * shipped default, matching `sheet-geometry`/`sheet-composer`).
7311
+ *
7312
+ * Existing sheets are IMMUTABLE — a changed preset applies to NEW windows only.
7313
+ * Each window's index sidecar carries its own tile dims, so a camera whose
7314
+ * preset changed over time renders every historical window at the dims it was
7315
+ * written with.
7316
+ */
7317
+ var ScrubThumbnailPresetSchema = _enum([
7318
+ "minimal",
7319
+ "low",
7320
+ "standard",
7321
+ "high",
7322
+ "max"
7323
+ ]);
7324
+ /**
7264
7325
  * The full per-camera recording intent — the wire shape of a RecordingTarget.
7265
7326
  *
7266
7327
  * `mode` is the authoritative storage choice; `schedule`/`triggers`/`pre`/`post`
@@ -7297,7 +7358,14 @@ var RecordingConfigSchema = object({
7297
7358
  * derived into bands once via `migrateConfigToBands`.
7298
7359
  */
7299
7360
  bands: array(RecordingBandSchema).optional(),
7300
- retention: RecordingRetentionSchema.optional()
7361
+ retention: RecordingRetentionSchema.optional(),
7362
+ /**
7363
+ * Per-camera scrub-thumbnail fidelity preset (resolution + JPEG quality for
7364
+ * timeline-scrub sprite previews). Absent = `standard`. Applies to NEW
7365
+ * windows only — existing sheets are immutable, and each window's index
7366
+ * carries its own tile dims so mixed-preset history renders correctly.
7367
+ */
7368
+ scrubThumbnails: ScrubThumbnailPresetSchema.optional()
7301
7369
  });
7302
7370
  /**
7303
7371
  * `StorageLocationType` — an addon-declared id that identifies the *kind* of
@@ -14655,7 +14723,11 @@ array(ZoneRuleSchema).readonly();
14655
14723
  * Extend the enum here when a new gating consumer comes online (audio
14656
14724
  * gating, alert filtering, …) — no other surface needs to change.
14657
14725
  */
14658
- var ZoneRuleStageEnum = _enum(["motion", "detection"]);
14726
+ var ZoneRuleStageEnum = _enum([
14727
+ "motion",
14728
+ "detection",
14729
+ "package"
14730
+ ]);
14659
14731
  /**
14660
14732
  * Runtime registry: cap-property-name → cap definition. `BaseDevice`'s
14661
14733
  * `state` getter looks up the cap definition here to construct a
@@ -14749,16 +14821,24 @@ var DEVICE_LOCAL_STATE_CAPS = {
14749
14821
  })
14750
14822
  },
14751
14823
  /**
14752
- * Runtime-state slice — both stages mirrored together so consumers
14824
+ * Runtime-state slice — every stage mirrored together so consumers
14753
14825
  * see one reactive handle (`device.state.zoneRules.value`) instead
14754
- * of two. Bulk-replace mutations on either stage write the full
14755
- * `{motion, detection}` shape, so subscribers always get the
14826
+ * of one per stage. Bulk-replace mutations on any stage write the full
14827
+ * `{motion, detection, package}` shape, so subscribers always get the
14756
14828
  * complete current set. Consumers that only care about one stage
14757
14829
  * just read the matching property.
14830
+ *
14831
+ * `package` backs the package-drop detector — a package zone is a
14832
+ * `ZoneRule` on the `'package'` stage referencing drawn polygons
14833
+ * (see docs/superpowers/specs/2026-07-17-package-zones-design.md §3.1).
14834
+ * The orchestrator provider that writes this stage lands in a later
14835
+ * slice; until then the mirror carries only `{motion, detection}` and
14836
+ * consumers read `package` as absent (treat as `[]`).
14758
14837
  */
14759
14838
  runtimeState: object({
14760
14839
  motion: array(ZoneRuleSchema).readonly(),
14761
- detection: array(ZoneRuleSchema).readonly()
14840
+ detection: array(ZoneRuleSchema).readonly(),
14841
+ package: array(ZoneRuleSchema).readonly()
14762
14842
  })
14763
14843
  },
14764
14844
  zones: zonesCapability
@@ -18786,6 +18866,7 @@ var EventKindIconSchema = _enum([
18786
18866
  "smoke",
18787
18867
  "water",
18788
18868
  "button",
18869
+ "package",
18789
18870
  "generic"
18790
18871
  ]);
18791
18872
  var EventKindCategorySchema = _enum([
@@ -18793,7 +18874,8 @@ var EventKindCategorySchema = _enum([
18793
18874
  "audio",
18794
18875
  "detection",
18795
18876
  "sensor",
18796
- "custom"
18877
+ "custom",
18878
+ "package"
18797
18879
  ]);
18798
18880
  var EventKindDescriptorSchema = object({
18799
18881
  /** Stable kind id (e.g. 'motion', 'person', 'contact'). */
@@ -22439,6 +22521,108 @@ method(object({
22439
22521
  auth: "admin"
22440
22522
  });
22441
22523
  /**
22524
+ * `recordingExport` cap — render a footage time range into a single downloadable
22525
+ * MP4 (regular / accelerated / decelerated / timelapse, ± audio), kept for a
22526
+ * bounded lifetime with a durable history, auto-expiry, and optional
22527
+ * delete-after-download.
22528
+ *
22529
+ * Like `recording` this is a `scope:'system', mode:'singleton'` cap: the
22530
+ * recorder self-gates so exactly ONE node (the designated `recordingNodeId`,
22531
+ * default `hub`) registers it. Device-scoped methods carry `deviceId` in their
22532
+ * input and dispatch to that single provider; the render runs on the node that
22533
+ * owns the footage (no cross-node segment transfer). Download rides the
22534
+ * framework addon data-plane. State persists in a DurableState blob (NOT
22535
+ * SQLite); history rows survive file deletion for audit.
22536
+ */
22537
+ /** Playback-speed multiplier for the render (1 = realtime). */
22538
+ var ExportSpeedSchema = number().min(.25).max(32);
22539
+ /** Timelapse cadence — sample one source frame per `everyMs`, output at `outputFps`. */
22540
+ var ExportTimelapseSchema = object({
22541
+ everyMs: number().int().positive(),
22542
+ outputFps: number().int().min(1).max(60).optional()
22543
+ });
22544
+ /**
22545
+ * Render options. `speed` and `timelapse` are mutually exclusive. `includeAudio`
22546
+ * is honoured only for a realtime-ish speed (0.5–2×); timelapse is always
22547
+ * silent. `maxLifeMs` bounds how long the finished file is kept;
22548
+ * `deleteAfterDownload` removes it shortly after the first complete download.
22549
+ */
22550
+ var ExportOptionsSchema = object({
22551
+ speed: ExportSpeedSchema.optional(),
22552
+ timelapse: ExportTimelapseSchema.optional(),
22553
+ includeAudio: boolean(),
22554
+ maxLifeMs: number().int().positive(),
22555
+ deleteAfterDownload: boolean(),
22556
+ title: string().max(200).optional()
22557
+ }).superRefine((v, ctx) => {
22558
+ if (v.speed !== void 0 && v.timelapse !== void 0) ctx.addIssue({
22559
+ code: ZodIssueCode.custom,
22560
+ message: "speed and timelapse are mutually exclusive",
22561
+ path: ["timelapse"]
22562
+ });
22563
+ });
22564
+ var ExportStateSchema = _enum([
22565
+ "queued",
22566
+ "rendering",
22567
+ "ready",
22568
+ "failed",
22569
+ "expired",
22570
+ "deleted"
22571
+ ]);
22572
+ /** One export job / history row. */
22573
+ var ExportRecordSchema = object({
22574
+ id: string(),
22575
+ deviceId: number(),
22576
+ profile: string(),
22577
+ fromMs: number(),
22578
+ toMs: number(),
22579
+ options: ExportOptionsSchema,
22580
+ state: ExportStateSchema,
22581
+ /** 0–100 while rendering; null otherwise. */
22582
+ progressPct: number().nullable(),
22583
+ /** File size once ready; null before. */
22584
+ fileBytes: number().nullable(),
22585
+ expiresAt: number(),
22586
+ deleteAfterDownload: boolean(),
22587
+ /** Epoch of the first complete download; null until then. */
22588
+ downloadedAt: number().nullable(),
22589
+ createdAt: number(),
22590
+ /** User id/name that requested the export. */
22591
+ createdBy: string(),
22592
+ /** Failure reason when state is 'failed'; null otherwise. */
22593
+ error: string().nullable()
22594
+ });
22595
+ /** Candidate download URLs (LAN first, then operator extra hosts). */
22596
+ var ExportDownloadSchema = object({
22597
+ url: string(),
22598
+ endpoints: array(string())
22599
+ });
22600
+ method(object({
22601
+ deviceId: number(),
22602
+ profile: string(),
22603
+ fromMs: number(),
22604
+ toMs: number(),
22605
+ options: ExportOptionsSchema
22606
+ }), ExportRecordSchema, {
22607
+ kind: "mutation",
22608
+ auth: "protected"
22609
+ }), method(object({ deviceId: number().optional() }), array(ExportRecordSchema), {
22610
+ kind: "query",
22611
+ auth: "protected"
22612
+ }), method(object({ exportId: string() }), ExportRecordSchema, {
22613
+ kind: "query",
22614
+ auth: "protected"
22615
+ }), method(object({ exportId: string() }), ExportRecordSchema, {
22616
+ kind: "mutation",
22617
+ auth: "protected"
22618
+ }), method(object({ exportId: string() }), ExportRecordSchema, {
22619
+ kind: "mutation",
22620
+ auth: "protected"
22621
+ }), method(object({ exportId: string() }), ExportDownloadSchema, {
22622
+ kind: "query",
22623
+ auth: "protected"
22624
+ });
22625
+ /**
22442
22626
  * One publishable camera stream as its OWNING PROVIDER describes it — the same
22443
22627
  * payload the (legacy) push `streamBroker.publishCameraStream` carried, minus
22444
22628
  * `deviceId`.
@@ -26362,6 +26546,42 @@ Object.freeze({
26362
26546
  addonId: null,
26363
26547
  access: "create"
26364
26548
  },
26549
+ "recordingExport.cancelExport": {
26550
+ capName: "recordingExport",
26551
+ capScope: "system",
26552
+ addonId: null,
26553
+ access: "create"
26554
+ },
26555
+ "recordingExport.createExport": {
26556
+ capName: "recordingExport",
26557
+ capScope: "system",
26558
+ addonId: null,
26559
+ access: "create"
26560
+ },
26561
+ "recordingExport.deleteExport": {
26562
+ capName: "recordingExport",
26563
+ capScope: "system",
26564
+ addonId: null,
26565
+ access: "delete"
26566
+ },
26567
+ "recordingExport.getDownloadUrl": {
26568
+ capName: "recordingExport",
26569
+ capScope: "system",
26570
+ addonId: null,
26571
+ access: "view"
26572
+ },
26573
+ "recordingExport.getExport": {
26574
+ capName: "recordingExport",
26575
+ capScope: "system",
26576
+ addonId: null,
26577
+ access: "view"
26578
+ },
26579
+ "recordingExport.listExports": {
26580
+ capName: "recordingExport",
26581
+ capScope: "system",
26582
+ addonId: null,
26583
+ access: "view"
26584
+ },
26365
26585
  "sceneMonitor.captureReference": {
26366
26586
  capName: "scene-monitor",
26367
26587
  capScope: "device",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@camstack/addon-provider-homematic",
3
- "version": "1.1.23",
3
+ "version": "1.1.25",
4
4
  "description": "Homematic / HomematicIP (CCU3 / RaspberryMatic) device-provider addon for CamStack — wraps the nodehomematic library",
5
5
  "keywords": [
6
6
  "camstack",