@camstack/addon-export-hap 1.2.109 → 1.2.111

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.
@@ -84,7 +84,7 @@ function carryForward(base, existing, keys) {
84
84
  return out;
85
85
  }
86
86
  //#endregion
87
- //#region ../types/dist/event-category-DAXzJeTX.mjs
87
+ //#region ../types/dist/event-category-ZyX6jcse.mjs
88
88
  var EventCategory = /* @__PURE__ */ function(EventCategory) {
89
89
  EventCategory["SystemBoot"] = "system.boot";
90
90
  EventCategory["SystemAddonsReady"] = "system.addons-ready";
@@ -645,7 +645,7 @@ var EventCategory = /* @__PURE__ */ function(EventCategory) {
645
645
  * a package zone — a newly-appeared stationary object of a package class
646
646
  * that cleared the class / zone / dwell / size gates. Payload:
647
647
  * `PipelineAnalyticsPackageDeliveredPayload` carrying `{ deviceId,
648
- * entryId, className, zoneIds, keyFrameMediaKey?, bbox, timestamp }`.
648
+ * entryId, className, zoneIds, keyFrameMediaId?, bbox, timestamp }`.
649
649
  * Telemetry (D8): the durable record is the `package-events` store row;
650
650
  * this bus topic drives notifier rules + live UI. See
651
651
  * docs/superpowers/specs/2026-07-17-package-zones-design.md §5.1.
@@ -5431,7 +5431,7 @@ var ZodIssueCode = {
5431
5431
  var ZodFirstPartyTypeKind;
5432
5432
  ZodFirstPartyTypeKind || (ZodFirstPartyTypeKind = {});
5433
5433
  //#endregion
5434
- //#region ../types/dist/sleep-BnujYGPe.mjs
5434
+ //#region ../types/dist/sleep-CnLyvg3w.mjs
5435
5435
  /**
5436
5436
  * The audio chunk plane's byte format, and the ONE expansion from a coded
5437
5437
  * window to float samples (D455).
@@ -7951,13 +7951,30 @@ new Set([
7951
7951
  "scene",
7952
7952
  "motion",
7953
7953
  "object",
7954
- "audio"
7954
+ "audio",
7955
+ "mosaic"
7955
7956
  ]);
7956
- new Set([
7957
+ /**
7958
+ * The owner types that are event tables, in the order the event-media resolver
7959
+ * should consider them. Exported so a consumer asking "is this key an event?"
7960
+ * does not re-spell the list and drift from it.
7961
+ */
7962
+ var EVENT_OWNER_TYPES = [
7957
7963
  "motion",
7958
7964
  "object",
7959
7965
  "audio"
7960
- ]);
7966
+ ];
7967
+ /**
7968
+ * The same list as a Zod enum, for the cap inputs that must NAME the table
7969
+ * (D482: `getEventMedia` / `listEventMedia` take an owner, and an owner is
7970
+ * `(table, id)`).
7971
+ *
7972
+ * Built FROM {@link EVENT_OWNER_TYPES} rather than re-spelled: a fourth event
7973
+ * table would otherwise be accepted by the codec and refused at the door, with
7974
+ * nothing failing until a caller asked.
7975
+ */
7976
+ var EventOwnerTypeSchema = _enum(EVENT_OWNER_TYPES);
7977
+ new Set(EVENT_OWNER_TYPES);
7961
7978
  var EncodeProfileSchema = object({
7962
7979
  video: object({
7963
7980
  codec: _enum([
@@ -13136,6 +13153,16 @@ method(object({
13136
13153
  filter: QueryFilterSchema.optional(),
13137
13154
  columns: array(string()).readonly().optional()
13138
13155
  }), array(SettingsRecordSchema).readonly()), method(object({
13156
+ namespace: string().optional(),
13157
+ collection: string(),
13158
+ /** Declared columns identifying the slot; must be covered by a UNIQUE
13159
+ * index on the collection. Never empty. */
13160
+ conflict: array(string()).readonly(),
13161
+ record: BulkRecordSchema
13162
+ }), object({
13163
+ /** The id the row HAS: assigned by SQLite on an insert, or the existing
13164
+ * row's own id on an update. */
13165
+ id: union([string(), number()]) }), { kind: "mutation" }), method(object({
13139
13166
  namespace: string().optional(),
13140
13167
  collection: string(),
13141
13168
  record: object({
@@ -13270,6 +13297,14 @@ method(_void(), EngineInfoSchema, { auth: "admin" }), method(object({
13270
13297
  id: union([string(), number()]) }), {
13271
13298
  kind: "mutation",
13272
13299
  auth: "admin"
13300
+ }), method(object({
13301
+ namespace: string().optional(),
13302
+ collection: string(),
13303
+ conflict: array(string()).readonly(),
13304
+ record: BulkRecordSchema
13305
+ }), object({ id: union([string(), number()]) }), {
13306
+ kind: "mutation",
13307
+ auth: "admin"
13273
13308
  }), method(object({
13274
13309
  namespace: string().optional(),
13275
13310
  collection: string(),
@@ -19567,8 +19602,8 @@ var TrackPositionSchema = object({
19567
19602
  var TrackSnapshotSchema = object({
19568
19603
  timestamp: number(),
19569
19604
  position: TrackPositionSchema,
19570
- /** MediaStore key; resolve via `getTrackMedia({ trackId })`. */
19571
- mediaKey: string()
19605
+ /** The snapshot's media row id (D482); resolve via `listTrackMedia({ trackId })`. */
19606
+ mediaId: number().int()
19572
19607
  });
19573
19608
  /**
19574
19609
  * Normalized 0..1 trajectory envelope (min/max over every position bbox,
@@ -20172,14 +20207,14 @@ var ObjectEventSchema = object({
20172
20207
  * pixel-space `bbox` onto a displayed image. Omitted in slim projection. */
20173
20208
  frameWidth: number().optional(),
20174
20209
  frameHeight: number().optional(),
20175
- /** MediaStore key for the crop attached to this event (if any). */
20176
- mediaKey: string().optional(),
20177
- /** Design B: MediaStore key of the track's native-resolution key frame (the
20210
+ /** Media row id of the crop attached to this event (if any) — D482. */
20211
+ mediaId: number().int().optional(),
20212
+ /** Design B: media row id of the track's native-resolution key frame (the
20178
20213
  * best-detection full frame). Resolve via the event-media data-plane
20179
- * (`/addon/<addonId>/event-media/<keyFrameMediaKey>`) for a detail view that
20214
+ * (`/addon/<addonId>/event-media/m/<keyFrameMediaId>`) for a detail view that
20180
20215
  * draws `bbox` over the native frame. Absent on legacy rows / non-decoded
20181
- * sources — consumers fall back to `mediaKey` (the tight crop). */
20182
- keyFrameMediaKey: string().optional(),
20216
+ * sources — consumers fall back to `mediaId` (the tight crop). */
20217
+ keyFrameMediaId: number().int().optional(),
20183
20218
  /** Populated by B5 (recording playback URL for this event). */
20184
20219
  mediaUrl: string().optional(),
20185
20220
  /** The parent track's key-event importance [0,1], propagated to every object
@@ -20229,7 +20264,7 @@ var MediaFileKindEnum = _enum([
20229
20264
  * hub-main's heap on the way past — for an `<img>` that would have cached it.
20230
20265
  *
20231
20266
  * `url` points at the `event-media` data plane
20232
- * (`/addon/<addonId>/event-media/<storedKey>`), which serves the same blob
20267
+ * (`/addon/<addonId>/event-media/m/<mediaId>` — D482), which serves the same blob
20233
20268
  * with an ETag and `Cache-Control: immutable`, honours conditional GETs, can
20234
20269
  * render a `?variant=thumb`, and streams. The hub gate in front of it requires
20235
20270
  * a bearer or the session cookie (`access: 'authenticated'`), so the bytes are
@@ -20237,10 +20272,13 @@ var MediaFileKindEnum = _enum([
20237
20272
  * `data-plane-access.ts` for the rule and the one gap it does not close
20238
20273
  * (per-device scoping).
20239
20274
  *
20240
- * The URL is built from the row's **stored** key, which is not always its
20241
- * published `kind`: a track's face/plate crop is stored as `crop` under
20275
+ * The URL is built from the row's own id, which since D482 says nothing about
20276
+ * the row's owner or kind — and that is the point. The published `kind` is not
20277
+ * the stored one (a track's face/plate crop is stored as `crop` under
20242
20278
  * `('face'|'plate', '<prefix>-<trackId>')` and published as
20243
- * `faceCrop`/`plateCrop`. `MediaStore.getByKey` knows only the stored key.
20279
+ * `faceCrop`/`plateCrop`), and under the old composite key the URL had to be
20280
+ * taken from `row.key` verbatim or it 404'd exactly those two rows. An id
20281
+ * cannot be mis-reconstructed, because there is nothing in it to reconstruct.
20244
20282
  *
20245
20283
  * ## `base64` is TRANSITIONAL and is going away
20246
20284
  *
@@ -20253,7 +20291,19 @@ var MediaFileKindEnum = _enum([
20253
20291
  * `analytics-query-facade.ts`; nothing else reads it.
20254
20292
  */
20255
20293
  var MediaFileSchema = object({
20256
- key: string(),
20294
+ /**
20295
+ * The media row's OWN id — the rowid SQLite assigned it (D482).
20296
+ *
20297
+ * It used to be the row's composite key, `<ownerType>:<ownerId>:<fileKind>
20298
+ * [:<timestampMs>]`, which embedded a FOREIGN id. D474 made event ids
20299
+ * per-table rowid aliases, so `object:101314` and `motion:101314` are two
20300
+ * different rows that spell the same number, and a track whose
20301
+ * `bestEventId` was an object event was served a motion event's snapshot —
20302
+ * a night-time thumbnail on an afternoon track, with no error anywhere. A
20303
+ * surrogate id cannot name the wrong row's owner, because it names no owner
20304
+ * at all.
20305
+ */
20306
+ mediaId: number().int(),
20257
20307
  kind: MediaFileKindEnum,
20258
20308
  sizeBytes: number(),
20259
20309
  timestamp: number()
@@ -20361,7 +20411,7 @@ var RetrainTrackSchema = object({
20361
20411
  });
20362
20412
  /** A frame the picker may offer — an index row, no blob was read to produce it. */
20363
20413
  var RetrainFrameCandidateSchema = object({
20364
- mediaKey: string(),
20414
+ mediaId: number().int(),
20365
20415
  kind: MediaFileKindEnum,
20366
20416
  timestamp: number(),
20367
20417
  sizeBytes: number().int(),
@@ -20375,7 +20425,7 @@ var RetrainFrameSchema = object({
20375
20425
  deviceId: number(),
20376
20426
  trackId: string(),
20377
20427
  /** Provenance only. It may already point at nothing — that is expected. */
20378
- sourceMediaKey: string(),
20428
+ sourceMediaId: number().int(),
20379
20429
  sourceKind: MediaFileKindEnum,
20380
20430
  sizeBytes: number().int(),
20381
20431
  width: number().int(),
@@ -20391,7 +20441,7 @@ var RetrainCopyRefusalSchema = _enum([
20391
20441
  var RetrainFrameSelectionSchema = object({
20392
20442
  copied: array(RetrainFrameSchema).readonly(),
20393
20443
  refused: array(object({
20394
- sourceMediaKey: string(),
20444
+ sourceMediaId: number().int(),
20395
20445
  reason: RetrainCopyRefusalSchema
20396
20446
  })).readonly()
20397
20447
  });
@@ -20399,7 +20449,7 @@ var RetrainFrameListSchema = object({
20399
20449
  candidates: array(RetrainFrameCandidateSchema).readonly(),
20400
20450
  copies: array(RetrainFrameSchema).readonly(),
20401
20451
  /** What the page pre-selects — the native key frame when one survives. */
20402
- autoPickMediaKey: string().optional()
20452
+ autoPickMediaId: number().int().optional()
20403
20453
  });
20404
20454
  /** What the operator asked the assist to look for. */
20405
20455
  var RetrainAssistSubjectSchema = discriminatedUnion("kind", [object({
@@ -20494,9 +20544,11 @@ var RecentTracksQueryInput = object({
20494
20544
  });
20495
20545
  /**
20496
20546
  * A Summary (D359): the per-camera session envelope that references tracks.
20497
- * `groupShotKey` is the media key of its group shot (`ownerKind: 'summary'`,
20498
- * served by the event-media plane by key), null until the first frame with a
20499
- * member visible has been materialised.
20547
+ * `groupShotMediaId` is the media ROW id of its group shot (`ownerKind:
20548
+ * 'summary'`, served by the event-media plane at `/m/<id>`), null until the
20549
+ * first frame with a member visible has been materialised (D482 — it was the
20550
+ * row's composite key, which embedded the owner and could name another table's
20551
+ * row once event ids became per-table rowids).
20500
20552
  */
20501
20553
  var SummarySchema = object({
20502
20554
  id: string(),
@@ -20505,7 +20557,7 @@ var SummarySchema = object({
20505
20557
  lastActiveAt: number(),
20506
20558
  sealedAt: number().nullable(),
20507
20559
  memberCount: number().int().nonnegative(),
20508
- groupShotKey: string().nullable()
20560
+ groupShotMediaId: number().int().nullable()
20509
20561
  });
20510
20562
  var SummariesQueryInput = object({
20511
20563
  deviceIds: array(number()).min(1),
@@ -21059,7 +21111,7 @@ DeviceType.Camera, method(object({ deviceId: number() }), array(TrackSchema).rea
21059
21111
  }), method(object({
21060
21112
  deviceId: number(),
21061
21113
  trackId: string(),
21062
- mediaKeys: array(string()).min(1)
21114
+ mediaIds: array(number().int()).min(1)
21063
21115
  }), RetrainFrameSelectionSchema, {
21064
21116
  kind: "mutation",
21065
21117
  auth: "admin"
@@ -21127,7 +21179,10 @@ DeviceType.Camera, method(object({ deviceId: number() }), array(TrackSchema).rea
21127
21179
  kind: "query",
21128
21180
  auth: "admin"
21129
21181
  }), method(object({
21130
- eventId: string(),
21182
+ /** Which of the three event tables owns the row. */
21183
+ ownerType: EventOwnerTypeSchema,
21184
+ /** The event's rowid in that table (D474: an event id is a number). */
21185
+ eventId: number().int(),
21131
21186
  kind: MediaFileKindEnum.optional(),
21132
21187
  deviceId: number()
21133
21188
  }), array(MediaFileSchema).readonly()), method(object({
@@ -21138,7 +21193,8 @@ DeviceType.Camera, method(object({ deviceId: number() }), array(TrackSchema).rea
21138
21193
  trackId: string(),
21139
21194
  deviceId: number()
21140
21195
  }), array(MediaFileInfoSchema).readonly()), method(object({
21141
- eventId: string(),
21196
+ ownerType: EventOwnerTypeSchema,
21197
+ eventId: number().int(),
21142
21198
  deviceId: number()
21143
21199
  }), array(MediaFileInfoSchema).readonly()), method(SearchObjectEventsInput, array(ScoredObjectEventSchema).readonly()), method(object({}), WipeObjectEmbeddingsResultSchema, {
21144
21200
  kind: "mutation",
@@ -25621,8 +25677,8 @@ var IdentitySchema = object({
25621
25677
  id: string(),
25622
25678
  name: string(),
25623
25679
  sampleCount: number().int().nonnegative(),
25624
- coverMediaKey: string().optional(),
25625
- /** Inline base64 of the cover sample's crop, resolved from `coverMediaKey`.
25680
+ coverMediaId: number().int().optional(),
25681
+ /** Inline base64 of the cover sample's crop, resolved from `coverMediaId`.
25626
25682
  * Lets the UI render the person's face avatar instead of a placeholder icon. */
25627
25683
  coverBase64: string().optional()
25628
25684
  });
@@ -25648,8 +25704,8 @@ var IdentitySampleAuditRowSchema = object({
25648
25704
  enrolledAt: number().int(),
25649
25705
  deviceId: number().int().optional(),
25650
25706
  source: _enum(["detected", "photo"]),
25651
- /** The enrolled crop's media key, so the panel can show the evidence. */
25652
- mediaKey: string().optional(),
25707
+ /** The enrolled crop's media row id, so the panel can show the evidence. */
25708
+ mediaId: number().int().optional(),
25653
25709
  /**
25654
25710
  * False = the sample is stamped with a DIFFERENT face model, and a cosine
25655
25711
  * across feature spaces is a well-formed float with no meaning. Every number
@@ -25718,15 +25774,15 @@ var FaceInfoSchema = object({
25718
25774
  */
25719
25775
  cropUrl: string().optional(),
25720
25776
  /** Design B: the face bbox (pixel space) on the key frame — lets a detail
25721
- * view draw the box over the native `keyFrameMediaKey` frame. Absent on
25777
+ * view draw the box over the native `keyFrameMediaId` frame. Absent on
25722
25778
  * legacy rows written before design B. */
25723
25779
  faceBbox: BoundingBoxSchema.optional(),
25724
25780
  /** Design B: MediaStore key of the track's native-resolution key frame.
25725
25781
  * Fetch the native JPEG via the event-media data-plane
25726
- * (`/addon/<addonId>/event-media/<keyFrameMediaKey>`). Absent when the
25782
+ * (`/addon/<addonId>/event-media/m/<keyFrameMediaId>`). Absent when the
25727
25783
  * track produced no key frame (e.g. native/onboard source) — the UI falls
25728
25784
  * back to the inline `base64` face crop. */
25729
- keyFrameMediaKey: string().optional(),
25785
+ keyFrameMediaId: number().int().optional(),
25730
25786
  /** Winning identity-match cosine (0..1) for this face's track, when an
25731
25787
  * identity was auto-confirmed. Lets the UI surface WHY a face was assigned
25732
25788
  * (confidence badge / low-confidence audit). Absent on legacy rows and on
@@ -25795,11 +25851,14 @@ var FaceClusterSchema = object({
25795
25851
  * `data:image/jpeg;base64,…` from a row whose `key` sat right beside it, in a
25796
25852
  * dialog already rendering its key FRAME from the `event-media` plane.
25797
25853
  *
25798
- * `url` is `/addon/<addonId>/event-media/<encoded key>`. The plane resolves a
25799
- * media key directly, so this needed no new plane and no new access decision.
25854
+ * `url` is `/addon/<addonId>/event-media/m/<mediaId>` (D482). The plane
25855
+ * resolves a media row by its own id, so this needed no new plane and no new
25856
+ * access decision.
25800
25857
  */
25801
25858
  var MediaFileLiteSchema$1 = object({
25802
- key: string(),
25859
+ /** The media row's own id — the same address `MediaFileRef.mediaId` carries
25860
+ * (D482). It was the row's composite key, which embedded the owner. */
25861
+ mediaId: number().int(),
25803
25862
  kind: string(),
25804
25863
  url: string(),
25805
25864
  sizeBytes: number(),
@@ -25816,7 +25875,7 @@ method(object({
25816
25875
  * covers inline, ~10 KB of base64 per row, on a query this UI mounts
25817
25876
  * four times and the viewer holds at `staleTime: 30_000`.
25818
25877
  *
25819
- * Nothing loses its avatar: `coverMediaKey` is already on every row and
25878
+ * Nothing loses its avatar: `coverMediaId` is already on every row and
25820
25879
  * the `event-media` plane serves that key `immutable` with an ETag.
25821
25880
  *
25822
25881
  * **This is an INPUT field, so it does not reach the addon until the
@@ -28300,8 +28359,8 @@ var VehicleSchema = object({
28300
28359
  id: string(),
28301
28360
  name: string(),
28302
28361
  sampleCount: number().int().nonnegative(),
28303
- coverMediaKey: string().optional(),
28304
- /** Inline base64 of the cover sample's plate crop, resolved from `coverMediaKey`. */
28362
+ coverMediaId: number().int().optional(),
28363
+ /** Inline base64 of the cover sample's plate crop, resolved from `coverMediaId`. */
28305
28364
  coverBase64: string().optional()
28306
28365
  });
28307
28366
  var VehicleSampleInfoSchema = object({
@@ -28334,7 +28393,7 @@ var PlateInfoSchema = object({
28334
28393
  /** keyFrame parity: the plate bbox (pixel space) on the native key frame. */
28335
28394
  plateBbox: BoundingBoxSchema.optional(),
28336
28395
  /** keyFrame parity: MediaStore key of the track's native-resolution key frame. */
28337
- keyFrameMediaKey: string().optional(),
28396
+ keyFrameMediaId: number().int().optional(),
28338
28397
  base64: string().optional(),
28339
28398
  /**
28340
28399
  * Same crop as a data-plane URL, always present when the plate has a stored
@@ -28355,11 +28414,14 @@ var PlateInfoSchema = object({
28355
28414
  * `data:image/jpeg;base64,…` from a row whose `key` sat right beside it, in a
28356
28415
  * dialog already rendering its key FRAME from the `event-media` plane.
28357
28416
  *
28358
- * `url` is `/addon/<addonId>/event-media/<encoded key>`. The plane resolves a
28359
- * media key directly, so this needed no new plane and no new access decision.
28417
+ * `url` is `/addon/<addonId>/event-media/m/<mediaId>` (D482). The plane
28418
+ * resolves a media row by its own id, so this needed no new plane and no new
28419
+ * access decision.
28360
28420
  */
28361
28421
  var MediaFileLiteSchema = object({
28362
- key: string(),
28422
+ /** The media row's own id — the same address `MediaFileRef.mediaId` carries
28423
+ * (D482). It was the row's composite key, which embedded the owner. */
28424
+ mediaId: number().int(),
28363
28425
  kind: string(),
28364
28426
  url: string(),
28365
28427
  sizeBytes: number(),
@@ -28418,7 +28480,7 @@ method(object({
28418
28480
  }), method(object({
28419
28481
  /** Inline {@link VehicleSchema.coverBase64} on every row. Default
28420
28482
  * `false` — the vehicle twin of `faceGallery.listIdentities`'s
28421
- * option; `coverMediaKey` + the `event-media` plane carry the picture.
28483
+ * option; `coverMediaId` + the `event-media` plane carry the picture.
28422
28484
  * INPUT field: stripped by the hub router until the train ships, which
28423
28485
  * resolves to `false` and is exactly the intended default. */
28424
28486
  includeCrops: boolean().optional() }).optional(), array(VehicleSchema).readonly()), method(object({ name: string().min(1) }), VehicleSchema, {
@@ -29742,7 +29804,14 @@ var SceneReferenceSchema = object({
29742
29804
  modelId: string(),
29743
29805
  condition: SceneConditionSchema,
29744
29806
  capturedAt: number(),
29745
- thumbnailMediaId: string().optional(),
29807
+ /**
29808
+ * The reference thumbnail's media ROW id (D482).
29809
+ *
29810
+ * The name already said `mediaId` while the value was the row's composite
29811
+ * key — a leftover that read as done and was not. It is the row's own id
29812
+ * now, addressed at `/addon/<addonId>/event-media/m/<id>`.
29813
+ */
29814
+ thumbnailMediaId: number().int().optional(),
29746
29815
  /** Whole-frame (downscaled) embedding captured alongside the ROI crop. The
29747
29816
  * anti-view-shift anchor: a bumped camera, a PTZ preset or a re-aim makes the
29748
29817
  * normalized rect frame a different piece of world, and the scene would
@@ -31215,7 +31284,7 @@ var StationaryObjectSchema = object({
31215
31284
  /** Enrichment label carried from the source track (identity / plate). */
31216
31285
  label: string().optional(),
31217
31286
  /** Native-resolution key-frame media key for the parked object's best image. */
31218
- keyFrameMediaKey: string().optional()
31287
+ keyFrameMediaId: number().int().optional()
31219
31288
  });
31220
31289
  var CameraOccupancySnapshotSchema = object({
31221
31290
  /** Frame timestamp of the inference result that produced this snapshot. */
@@ -32371,6 +32440,12 @@ Object.freeze({
32371
32440
  addonId: null,
32372
32441
  access: "create"
32373
32442
  },
32443
+ "dataStoreProvider.upsert": {
32444
+ capName: "data-store-provider",
32445
+ capScope: "system",
32446
+ addonId: null,
32447
+ access: "create"
32448
+ },
32374
32449
  "dayNight.getOptions": {
32375
32450
  capName: "day-night",
32376
32451
  capScope: "device",
@@ -36235,6 +36310,12 @@ Object.freeze({
36235
36310
  addonId: null,
36236
36311
  access: "create"
36237
36312
  },
36313
+ "settingsStore.upsert": {
36314
+ capName: "settings-store",
36315
+ capScope: "system",
36316
+ addonId: null,
36317
+ access: "create"
36318
+ },
36238
36319
  "smtpProvider.getStatus": {
36239
36320
  capName: "smtp-provider",
36240
36321
  capScope: "system",
@@ -72,7 +72,7 @@ function carryForward(base, existing, keys) {
72
72
  return out;
73
73
  }
74
74
  //#endregion
75
- //#region ../types/dist/event-category-DAXzJeTX.mjs
75
+ //#region ../types/dist/event-category-ZyX6jcse.mjs
76
76
  var EventCategory = /* @__PURE__ */ function(EventCategory) {
77
77
  EventCategory["SystemBoot"] = "system.boot";
78
78
  EventCategory["SystemAddonsReady"] = "system.addons-ready";
@@ -633,7 +633,7 @@ var EventCategory = /* @__PURE__ */ function(EventCategory) {
633
633
  * a package zone — a newly-appeared stationary object of a package class
634
634
  * that cleared the class / zone / dwell / size gates. Payload:
635
635
  * `PipelineAnalyticsPackageDeliveredPayload` carrying `{ deviceId,
636
- * entryId, className, zoneIds, keyFrameMediaKey?, bbox, timestamp }`.
636
+ * entryId, className, zoneIds, keyFrameMediaId?, bbox, timestamp }`.
637
637
  * Telemetry (D8): the durable record is the `package-events` store row;
638
638
  * this bus topic drives notifier rules + live UI. See
639
639
  * docs/superpowers/specs/2026-07-17-package-zones-design.md §5.1.
@@ -5419,7 +5419,7 @@ var ZodIssueCode = {
5419
5419
  var ZodFirstPartyTypeKind;
5420
5420
  ZodFirstPartyTypeKind || (ZodFirstPartyTypeKind = {});
5421
5421
  //#endregion
5422
- //#region ../types/dist/sleep-BnujYGPe.mjs
5422
+ //#region ../types/dist/sleep-CnLyvg3w.mjs
5423
5423
  /**
5424
5424
  * The audio chunk plane's byte format, and the ONE expansion from a coded
5425
5425
  * window to float samples (D455).
@@ -7939,13 +7939,30 @@ new Set([
7939
7939
  "scene",
7940
7940
  "motion",
7941
7941
  "object",
7942
- "audio"
7942
+ "audio",
7943
+ "mosaic"
7943
7944
  ]);
7944
- new Set([
7945
+ /**
7946
+ * The owner types that are event tables, in the order the event-media resolver
7947
+ * should consider them. Exported so a consumer asking "is this key an event?"
7948
+ * does not re-spell the list and drift from it.
7949
+ */
7950
+ var EVENT_OWNER_TYPES = [
7945
7951
  "motion",
7946
7952
  "object",
7947
7953
  "audio"
7948
- ]);
7954
+ ];
7955
+ /**
7956
+ * The same list as a Zod enum, for the cap inputs that must NAME the table
7957
+ * (D482: `getEventMedia` / `listEventMedia` take an owner, and an owner is
7958
+ * `(table, id)`).
7959
+ *
7960
+ * Built FROM {@link EVENT_OWNER_TYPES} rather than re-spelled: a fourth event
7961
+ * table would otherwise be accepted by the codec and refused at the door, with
7962
+ * nothing failing until a caller asked.
7963
+ */
7964
+ var EventOwnerTypeSchema = _enum(EVENT_OWNER_TYPES);
7965
+ new Set(EVENT_OWNER_TYPES);
7949
7966
  var EncodeProfileSchema = object({
7950
7967
  video: object({
7951
7968
  codec: _enum([
@@ -13124,6 +13141,16 @@ method(object({
13124
13141
  filter: QueryFilterSchema.optional(),
13125
13142
  columns: array(string()).readonly().optional()
13126
13143
  }), array(SettingsRecordSchema).readonly()), method(object({
13144
+ namespace: string().optional(),
13145
+ collection: string(),
13146
+ /** Declared columns identifying the slot; must be covered by a UNIQUE
13147
+ * index on the collection. Never empty. */
13148
+ conflict: array(string()).readonly(),
13149
+ record: BulkRecordSchema
13150
+ }), object({
13151
+ /** The id the row HAS: assigned by SQLite on an insert, or the existing
13152
+ * row's own id on an update. */
13153
+ id: union([string(), number()]) }), { kind: "mutation" }), method(object({
13127
13154
  namespace: string().optional(),
13128
13155
  collection: string(),
13129
13156
  record: object({
@@ -13258,6 +13285,14 @@ method(_void(), EngineInfoSchema, { auth: "admin" }), method(object({
13258
13285
  id: union([string(), number()]) }), {
13259
13286
  kind: "mutation",
13260
13287
  auth: "admin"
13288
+ }), method(object({
13289
+ namespace: string().optional(),
13290
+ collection: string(),
13291
+ conflict: array(string()).readonly(),
13292
+ record: BulkRecordSchema
13293
+ }), object({ id: union([string(), number()]) }), {
13294
+ kind: "mutation",
13295
+ auth: "admin"
13261
13296
  }), method(object({
13262
13297
  namespace: string().optional(),
13263
13298
  collection: string(),
@@ -19555,8 +19590,8 @@ var TrackPositionSchema = object({
19555
19590
  var TrackSnapshotSchema = object({
19556
19591
  timestamp: number(),
19557
19592
  position: TrackPositionSchema,
19558
- /** MediaStore key; resolve via `getTrackMedia({ trackId })`. */
19559
- mediaKey: string()
19593
+ /** The snapshot's media row id (D482); resolve via `listTrackMedia({ trackId })`. */
19594
+ mediaId: number().int()
19560
19595
  });
19561
19596
  /**
19562
19597
  * Normalized 0..1 trajectory envelope (min/max over every position bbox,
@@ -20160,14 +20195,14 @@ var ObjectEventSchema = object({
20160
20195
  * pixel-space `bbox` onto a displayed image. Omitted in slim projection. */
20161
20196
  frameWidth: number().optional(),
20162
20197
  frameHeight: number().optional(),
20163
- /** MediaStore key for the crop attached to this event (if any). */
20164
- mediaKey: string().optional(),
20165
- /** Design B: MediaStore key of the track's native-resolution key frame (the
20198
+ /** Media row id of the crop attached to this event (if any) — D482. */
20199
+ mediaId: number().int().optional(),
20200
+ /** Design B: media row id of the track's native-resolution key frame (the
20166
20201
  * best-detection full frame). Resolve via the event-media data-plane
20167
- * (`/addon/<addonId>/event-media/<keyFrameMediaKey>`) for a detail view that
20202
+ * (`/addon/<addonId>/event-media/m/<keyFrameMediaId>`) for a detail view that
20168
20203
  * draws `bbox` over the native frame. Absent on legacy rows / non-decoded
20169
- * sources — consumers fall back to `mediaKey` (the tight crop). */
20170
- keyFrameMediaKey: string().optional(),
20204
+ * sources — consumers fall back to `mediaId` (the tight crop). */
20205
+ keyFrameMediaId: number().int().optional(),
20171
20206
  /** Populated by B5 (recording playback URL for this event). */
20172
20207
  mediaUrl: string().optional(),
20173
20208
  /** The parent track's key-event importance [0,1], propagated to every object
@@ -20217,7 +20252,7 @@ var MediaFileKindEnum = _enum([
20217
20252
  * hub-main's heap on the way past — for an `<img>` that would have cached it.
20218
20253
  *
20219
20254
  * `url` points at the `event-media` data plane
20220
- * (`/addon/<addonId>/event-media/<storedKey>`), which serves the same blob
20255
+ * (`/addon/<addonId>/event-media/m/<mediaId>` — D482), which serves the same blob
20221
20256
  * with an ETag and `Cache-Control: immutable`, honours conditional GETs, can
20222
20257
  * render a `?variant=thumb`, and streams. The hub gate in front of it requires
20223
20258
  * a bearer or the session cookie (`access: 'authenticated'`), so the bytes are
@@ -20225,10 +20260,13 @@ var MediaFileKindEnum = _enum([
20225
20260
  * `data-plane-access.ts` for the rule and the one gap it does not close
20226
20261
  * (per-device scoping).
20227
20262
  *
20228
- * The URL is built from the row's **stored** key, which is not always its
20229
- * published `kind`: a track's face/plate crop is stored as `crop` under
20263
+ * The URL is built from the row's own id, which since D482 says nothing about
20264
+ * the row's owner or kind — and that is the point. The published `kind` is not
20265
+ * the stored one (a track's face/plate crop is stored as `crop` under
20230
20266
  * `('face'|'plate', '<prefix>-<trackId>')` and published as
20231
- * `faceCrop`/`plateCrop`. `MediaStore.getByKey` knows only the stored key.
20267
+ * `faceCrop`/`plateCrop`), and under the old composite key the URL had to be
20268
+ * taken from `row.key` verbatim or it 404'd exactly those two rows. An id
20269
+ * cannot be mis-reconstructed, because there is nothing in it to reconstruct.
20232
20270
  *
20233
20271
  * ## `base64` is TRANSITIONAL and is going away
20234
20272
  *
@@ -20241,7 +20279,19 @@ var MediaFileKindEnum = _enum([
20241
20279
  * `analytics-query-facade.ts`; nothing else reads it.
20242
20280
  */
20243
20281
  var MediaFileSchema = object({
20244
- key: string(),
20282
+ /**
20283
+ * The media row's OWN id — the rowid SQLite assigned it (D482).
20284
+ *
20285
+ * It used to be the row's composite key, `<ownerType>:<ownerId>:<fileKind>
20286
+ * [:<timestampMs>]`, which embedded a FOREIGN id. D474 made event ids
20287
+ * per-table rowid aliases, so `object:101314` and `motion:101314` are two
20288
+ * different rows that spell the same number, and a track whose
20289
+ * `bestEventId` was an object event was served a motion event's snapshot —
20290
+ * a night-time thumbnail on an afternoon track, with no error anywhere. A
20291
+ * surrogate id cannot name the wrong row's owner, because it names no owner
20292
+ * at all.
20293
+ */
20294
+ mediaId: number().int(),
20245
20295
  kind: MediaFileKindEnum,
20246
20296
  sizeBytes: number(),
20247
20297
  timestamp: number()
@@ -20349,7 +20399,7 @@ var RetrainTrackSchema = object({
20349
20399
  });
20350
20400
  /** A frame the picker may offer — an index row, no blob was read to produce it. */
20351
20401
  var RetrainFrameCandidateSchema = object({
20352
- mediaKey: string(),
20402
+ mediaId: number().int(),
20353
20403
  kind: MediaFileKindEnum,
20354
20404
  timestamp: number(),
20355
20405
  sizeBytes: number().int(),
@@ -20363,7 +20413,7 @@ var RetrainFrameSchema = object({
20363
20413
  deviceId: number(),
20364
20414
  trackId: string(),
20365
20415
  /** Provenance only. It may already point at nothing — that is expected. */
20366
- sourceMediaKey: string(),
20416
+ sourceMediaId: number().int(),
20367
20417
  sourceKind: MediaFileKindEnum,
20368
20418
  sizeBytes: number().int(),
20369
20419
  width: number().int(),
@@ -20379,7 +20429,7 @@ var RetrainCopyRefusalSchema = _enum([
20379
20429
  var RetrainFrameSelectionSchema = object({
20380
20430
  copied: array(RetrainFrameSchema).readonly(),
20381
20431
  refused: array(object({
20382
- sourceMediaKey: string(),
20432
+ sourceMediaId: number().int(),
20383
20433
  reason: RetrainCopyRefusalSchema
20384
20434
  })).readonly()
20385
20435
  });
@@ -20387,7 +20437,7 @@ var RetrainFrameListSchema = object({
20387
20437
  candidates: array(RetrainFrameCandidateSchema).readonly(),
20388
20438
  copies: array(RetrainFrameSchema).readonly(),
20389
20439
  /** What the page pre-selects — the native key frame when one survives. */
20390
- autoPickMediaKey: string().optional()
20440
+ autoPickMediaId: number().int().optional()
20391
20441
  });
20392
20442
  /** What the operator asked the assist to look for. */
20393
20443
  var RetrainAssistSubjectSchema = discriminatedUnion("kind", [object({
@@ -20482,9 +20532,11 @@ var RecentTracksQueryInput = object({
20482
20532
  });
20483
20533
  /**
20484
20534
  * A Summary (D359): the per-camera session envelope that references tracks.
20485
- * `groupShotKey` is the media key of its group shot (`ownerKind: 'summary'`,
20486
- * served by the event-media plane by key), null until the first frame with a
20487
- * member visible has been materialised.
20535
+ * `groupShotMediaId` is the media ROW id of its group shot (`ownerKind:
20536
+ * 'summary'`, served by the event-media plane at `/m/<id>`), null until the
20537
+ * first frame with a member visible has been materialised (D482 — it was the
20538
+ * row's composite key, which embedded the owner and could name another table's
20539
+ * row once event ids became per-table rowids).
20488
20540
  */
20489
20541
  var SummarySchema = object({
20490
20542
  id: string(),
@@ -20493,7 +20545,7 @@ var SummarySchema = object({
20493
20545
  lastActiveAt: number(),
20494
20546
  sealedAt: number().nullable(),
20495
20547
  memberCount: number().int().nonnegative(),
20496
- groupShotKey: string().nullable()
20548
+ groupShotMediaId: number().int().nullable()
20497
20549
  });
20498
20550
  var SummariesQueryInput = object({
20499
20551
  deviceIds: array(number()).min(1),
@@ -21047,7 +21099,7 @@ DeviceType.Camera, method(object({ deviceId: number() }), array(TrackSchema).rea
21047
21099
  }), method(object({
21048
21100
  deviceId: number(),
21049
21101
  trackId: string(),
21050
- mediaKeys: array(string()).min(1)
21102
+ mediaIds: array(number().int()).min(1)
21051
21103
  }), RetrainFrameSelectionSchema, {
21052
21104
  kind: "mutation",
21053
21105
  auth: "admin"
@@ -21115,7 +21167,10 @@ DeviceType.Camera, method(object({ deviceId: number() }), array(TrackSchema).rea
21115
21167
  kind: "query",
21116
21168
  auth: "admin"
21117
21169
  }), method(object({
21118
- eventId: string(),
21170
+ /** Which of the three event tables owns the row. */
21171
+ ownerType: EventOwnerTypeSchema,
21172
+ /** The event's rowid in that table (D474: an event id is a number). */
21173
+ eventId: number().int(),
21119
21174
  kind: MediaFileKindEnum.optional(),
21120
21175
  deviceId: number()
21121
21176
  }), array(MediaFileSchema).readonly()), method(object({
@@ -21126,7 +21181,8 @@ DeviceType.Camera, method(object({ deviceId: number() }), array(TrackSchema).rea
21126
21181
  trackId: string(),
21127
21182
  deviceId: number()
21128
21183
  }), array(MediaFileInfoSchema).readonly()), method(object({
21129
- eventId: string(),
21184
+ ownerType: EventOwnerTypeSchema,
21185
+ eventId: number().int(),
21130
21186
  deviceId: number()
21131
21187
  }), array(MediaFileInfoSchema).readonly()), method(SearchObjectEventsInput, array(ScoredObjectEventSchema).readonly()), method(object({}), WipeObjectEmbeddingsResultSchema, {
21132
21188
  kind: "mutation",
@@ -25609,8 +25665,8 @@ var IdentitySchema = object({
25609
25665
  id: string(),
25610
25666
  name: string(),
25611
25667
  sampleCount: number().int().nonnegative(),
25612
- coverMediaKey: string().optional(),
25613
- /** Inline base64 of the cover sample's crop, resolved from `coverMediaKey`.
25668
+ coverMediaId: number().int().optional(),
25669
+ /** Inline base64 of the cover sample's crop, resolved from `coverMediaId`.
25614
25670
  * Lets the UI render the person's face avatar instead of a placeholder icon. */
25615
25671
  coverBase64: string().optional()
25616
25672
  });
@@ -25636,8 +25692,8 @@ var IdentitySampleAuditRowSchema = object({
25636
25692
  enrolledAt: number().int(),
25637
25693
  deviceId: number().int().optional(),
25638
25694
  source: _enum(["detected", "photo"]),
25639
- /** The enrolled crop's media key, so the panel can show the evidence. */
25640
- mediaKey: string().optional(),
25695
+ /** The enrolled crop's media row id, so the panel can show the evidence. */
25696
+ mediaId: number().int().optional(),
25641
25697
  /**
25642
25698
  * False = the sample is stamped with a DIFFERENT face model, and a cosine
25643
25699
  * across feature spaces is a well-formed float with no meaning. Every number
@@ -25706,15 +25762,15 @@ var FaceInfoSchema = object({
25706
25762
  */
25707
25763
  cropUrl: string().optional(),
25708
25764
  /** Design B: the face bbox (pixel space) on the key frame — lets a detail
25709
- * view draw the box over the native `keyFrameMediaKey` frame. Absent on
25765
+ * view draw the box over the native `keyFrameMediaId` frame. Absent on
25710
25766
  * legacy rows written before design B. */
25711
25767
  faceBbox: BoundingBoxSchema.optional(),
25712
25768
  /** Design B: MediaStore key of the track's native-resolution key frame.
25713
25769
  * Fetch the native JPEG via the event-media data-plane
25714
- * (`/addon/<addonId>/event-media/<keyFrameMediaKey>`). Absent when the
25770
+ * (`/addon/<addonId>/event-media/m/<keyFrameMediaId>`). Absent when the
25715
25771
  * track produced no key frame (e.g. native/onboard source) — the UI falls
25716
25772
  * back to the inline `base64` face crop. */
25717
- keyFrameMediaKey: string().optional(),
25773
+ keyFrameMediaId: number().int().optional(),
25718
25774
  /** Winning identity-match cosine (0..1) for this face's track, when an
25719
25775
  * identity was auto-confirmed. Lets the UI surface WHY a face was assigned
25720
25776
  * (confidence badge / low-confidence audit). Absent on legacy rows and on
@@ -25783,11 +25839,14 @@ var FaceClusterSchema = object({
25783
25839
  * `data:image/jpeg;base64,…` from a row whose `key` sat right beside it, in a
25784
25840
  * dialog already rendering its key FRAME from the `event-media` plane.
25785
25841
  *
25786
- * `url` is `/addon/<addonId>/event-media/<encoded key>`. The plane resolves a
25787
- * media key directly, so this needed no new plane and no new access decision.
25842
+ * `url` is `/addon/<addonId>/event-media/m/<mediaId>` (D482). The plane
25843
+ * resolves a media row by its own id, so this needed no new plane and no new
25844
+ * access decision.
25788
25845
  */
25789
25846
  var MediaFileLiteSchema$1 = object({
25790
- key: string(),
25847
+ /** The media row's own id — the same address `MediaFileRef.mediaId` carries
25848
+ * (D482). It was the row's composite key, which embedded the owner. */
25849
+ mediaId: number().int(),
25791
25850
  kind: string(),
25792
25851
  url: string(),
25793
25852
  sizeBytes: number(),
@@ -25804,7 +25863,7 @@ method(object({
25804
25863
  * covers inline, ~10 KB of base64 per row, on a query this UI mounts
25805
25864
  * four times and the viewer holds at `staleTime: 30_000`.
25806
25865
  *
25807
- * Nothing loses its avatar: `coverMediaKey` is already on every row and
25866
+ * Nothing loses its avatar: `coverMediaId` is already on every row and
25808
25867
  * the `event-media` plane serves that key `immutable` with an ETag.
25809
25868
  *
25810
25869
  * **This is an INPUT field, so it does not reach the addon until the
@@ -28288,8 +28347,8 @@ var VehicleSchema = object({
28288
28347
  id: string(),
28289
28348
  name: string(),
28290
28349
  sampleCount: number().int().nonnegative(),
28291
- coverMediaKey: string().optional(),
28292
- /** Inline base64 of the cover sample's plate crop, resolved from `coverMediaKey`. */
28350
+ coverMediaId: number().int().optional(),
28351
+ /** Inline base64 of the cover sample's plate crop, resolved from `coverMediaId`. */
28293
28352
  coverBase64: string().optional()
28294
28353
  });
28295
28354
  var VehicleSampleInfoSchema = object({
@@ -28322,7 +28381,7 @@ var PlateInfoSchema = object({
28322
28381
  /** keyFrame parity: the plate bbox (pixel space) on the native key frame. */
28323
28382
  plateBbox: BoundingBoxSchema.optional(),
28324
28383
  /** keyFrame parity: MediaStore key of the track's native-resolution key frame. */
28325
- keyFrameMediaKey: string().optional(),
28384
+ keyFrameMediaId: number().int().optional(),
28326
28385
  base64: string().optional(),
28327
28386
  /**
28328
28387
  * Same crop as a data-plane URL, always present when the plate has a stored
@@ -28343,11 +28402,14 @@ var PlateInfoSchema = object({
28343
28402
  * `data:image/jpeg;base64,…` from a row whose `key` sat right beside it, in a
28344
28403
  * dialog already rendering its key FRAME from the `event-media` plane.
28345
28404
  *
28346
- * `url` is `/addon/<addonId>/event-media/<encoded key>`. The plane resolves a
28347
- * media key directly, so this needed no new plane and no new access decision.
28405
+ * `url` is `/addon/<addonId>/event-media/m/<mediaId>` (D482). The plane
28406
+ * resolves a media row by its own id, so this needed no new plane and no new
28407
+ * access decision.
28348
28408
  */
28349
28409
  var MediaFileLiteSchema = object({
28350
- key: string(),
28410
+ /** The media row's own id — the same address `MediaFileRef.mediaId` carries
28411
+ * (D482). It was the row's composite key, which embedded the owner. */
28412
+ mediaId: number().int(),
28351
28413
  kind: string(),
28352
28414
  url: string(),
28353
28415
  sizeBytes: number(),
@@ -28406,7 +28468,7 @@ method(object({
28406
28468
  }), method(object({
28407
28469
  /** Inline {@link VehicleSchema.coverBase64} on every row. Default
28408
28470
  * `false` — the vehicle twin of `faceGallery.listIdentities`'s
28409
- * option; `coverMediaKey` + the `event-media` plane carry the picture.
28471
+ * option; `coverMediaId` + the `event-media` plane carry the picture.
28410
28472
  * INPUT field: stripped by the hub router until the train ships, which
28411
28473
  * resolves to `false` and is exactly the intended default. */
28412
28474
  includeCrops: boolean().optional() }).optional(), array(VehicleSchema).readonly()), method(object({ name: string().min(1) }), VehicleSchema, {
@@ -29730,7 +29792,14 @@ var SceneReferenceSchema = object({
29730
29792
  modelId: string(),
29731
29793
  condition: SceneConditionSchema,
29732
29794
  capturedAt: number(),
29733
- thumbnailMediaId: string().optional(),
29795
+ /**
29796
+ * The reference thumbnail's media ROW id (D482).
29797
+ *
29798
+ * The name already said `mediaId` while the value was the row's composite
29799
+ * key — a leftover that read as done and was not. It is the row's own id
29800
+ * now, addressed at `/addon/<addonId>/event-media/m/<id>`.
29801
+ */
29802
+ thumbnailMediaId: number().int().optional(),
29734
29803
  /** Whole-frame (downscaled) embedding captured alongside the ROI crop. The
29735
29804
  * anti-view-shift anchor: a bumped camera, a PTZ preset or a re-aim makes the
29736
29805
  * normalized rect frame a different piece of world, and the scene would
@@ -31203,7 +31272,7 @@ var StationaryObjectSchema = object({
31203
31272
  /** Enrichment label carried from the source track (identity / plate). */
31204
31273
  label: string().optional(),
31205
31274
  /** Native-resolution key-frame media key for the parked object's best image. */
31206
- keyFrameMediaKey: string().optional()
31275
+ keyFrameMediaId: number().int().optional()
31207
31276
  });
31208
31277
  var CameraOccupancySnapshotSchema = object({
31209
31278
  /** Frame timestamp of the inference result that produced this snapshot. */
@@ -32359,6 +32428,12 @@ Object.freeze({
32359
32428
  addonId: null,
32360
32429
  access: "create"
32361
32430
  },
32431
+ "dataStoreProvider.upsert": {
32432
+ capName: "data-store-provider",
32433
+ capScope: "system",
32434
+ addonId: null,
32435
+ access: "create"
32436
+ },
32362
32437
  "dayNight.getOptions": {
32363
32438
  capName: "day-night",
32364
32439
  capScope: "device",
@@ -36223,6 +36298,12 @@ Object.freeze({
36223
36298
  addonId: null,
36224
36299
  access: "create"
36225
36300
  },
36301
+ "settingsStore.upsert": {
36302
+ capName: "settings-store",
36303
+ capScope: "system",
36304
+ addonId: null,
36305
+ access: "create"
36306
+ },
36226
36307
  "smtpProvider.getStatus": {
36227
36308
  capName: "smtp-provider",
36228
36309
  capScope: "system",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@camstack/addon-export-hap",
3
- "version": "1.2.109",
3
+ "version": "1.2.111",
4
4
  "description": "HomeKit (HAP) exporter for CamStack devices. Publishes each exposed device as its own HomeKit accessory: cameras and doorbells with SRTP streaming, HomeKit Secure Video, motion, two-way audio, PTZ and battery; switches, lights, locks and sensors through a capability→service table.",
5
5
  "keywords": [
6
6
  "camstack",