@camstack/addon-import-alexa 0.1.23 → 0.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
@@ -7514,6 +7514,62 @@ var RecordingConfigSchema = object({
7514
7514
  scrubThumbnails: ScrubThumbnailPresetSchema.optional()
7515
7515
  });
7516
7516
  /**
7517
+ * Ops-log — the durable, append-only operations audit shared by the
7518
+ * recordings and events management surfaces.
7519
+ *
7520
+ * ONE row shape is reused for both domains so a single "Activity" view can
7521
+ * merge the recorder's DurableState ring (recordings ops-log) and the
7522
+ * pipeline-analytics SQLite collection (events ops-log). Each row records a
7523
+ * management operation, WHY it ran (reason), and its measurable effect
7524
+ * (itemsAffected + bytesReclaimed). Writes are best-effort — a failed log must
7525
+ * never fail the operation it records.
7526
+ */
7527
+ /** Which management domain the operation belongs to. */
7528
+ var OpsLogDomainSchema = _enum(["recording", "events"]);
7529
+ /** The kind of management operation performed. */
7530
+ var OpsLogOpSchema = _enum([
7531
+ "prune",
7532
+ "manual-delete",
7533
+ "rescan",
7534
+ "retention-run"
7535
+ ]);
7536
+ /** Why the operation ran. */
7537
+ var OpsLogReasonSchema = _enum([
7538
+ "retention",
7539
+ "quota",
7540
+ "manual",
7541
+ "operator"
7542
+ ]);
7543
+ /** One audit row, shared verbatim by both domains. */
7544
+ var OpsLogEntrySchema = object({
7545
+ /** Unique row id. */
7546
+ id: string(),
7547
+ /** Epoch ms the operation completed. */
7548
+ at: number(),
7549
+ domain: OpsLogDomainSchema,
7550
+ op: OpsLogOpSchema,
7551
+ reason: OpsLogReasonSchema,
7552
+ /** The camera the op targeted; null for a cluster/global op. */
7553
+ deviceId: number().nullable(),
7554
+ /** Node that performed the op (the log carries nodeId — no cross-node aggregation). */
7555
+ nodeId: string(),
7556
+ /** Buckets / rows deleted (op-specific unit). */
7557
+ itemsAffected: number(),
7558
+ /** Bytes reclaimed by the op (0 when not measurable). */
7559
+ bytesReclaimed: number(),
7560
+ /** Free-text detail (e.g. "floor moved to <ts>"); null when none. */
7561
+ detail: string().nullable(),
7562
+ /** Who/what triggered the op. */
7563
+ actor: string()
7564
+ });
7565
+ /** Shared query input for the per-domain `listOpsLog` cap methods. */
7566
+ var OpsLogQueryInputSchema = object({
7567
+ /** Restrict to a single camera; omit for every row. */
7568
+ deviceId: number().optional(),
7569
+ /** Max rows returned, newest-first. */
7570
+ limit: number().int().min(1).max(1e3).optional()
7571
+ });
7572
+ /**
7517
7573
  * `StorageLocationType` — an addon-declared id that identifies the *kind* of
7518
7574
  * storage a location serves. Defined here (not in `capabilities/storage.cap.ts`)
7519
7575
  * so the persisted record schema and the consumer-facing cap can both consume it
@@ -7757,6 +7813,160 @@ var EncodeProfileSchema = object({
7757
7813
  */
7758
7814
  outputArgs: array(string()).optional()
7759
7815
  });
7816
+ var COCO_TO_MACRO = {
7817
+ mapping: {
7818
+ person: "person",
7819
+ bicycle: "vehicle",
7820
+ car: "vehicle",
7821
+ motorcycle: "vehicle",
7822
+ airplane: "vehicle",
7823
+ bus: "vehicle",
7824
+ train: "vehicle",
7825
+ truck: "vehicle",
7826
+ boat: "vehicle",
7827
+ bird: "animal",
7828
+ cat: "animal",
7829
+ dog: "animal",
7830
+ horse: "animal",
7831
+ sheep: "animal",
7832
+ cow: "animal",
7833
+ elephant: "animal",
7834
+ bear: "animal",
7835
+ zebra: "animal",
7836
+ giraffe: "animal",
7837
+ suitcase: "package",
7838
+ backpack: "package",
7839
+ handbag: "package"
7840
+ },
7841
+ preserveOriginal: false
7842
+ };
7843
+ var AUDIO_MACRO_LABELS = [
7844
+ {
7845
+ id: "speech",
7846
+ name: "Speech",
7847
+ icon: "🗣️"
7848
+ },
7849
+ {
7850
+ id: "scream",
7851
+ name: "Scream / Shout",
7852
+ icon: "😱"
7853
+ },
7854
+ {
7855
+ id: "crying",
7856
+ name: "Crying / Baby",
7857
+ icon: "😢"
7858
+ },
7859
+ {
7860
+ id: "laughter",
7861
+ name: "Laughter",
7862
+ icon: "😂"
7863
+ },
7864
+ {
7865
+ id: "music",
7866
+ name: "Music",
7867
+ icon: "🎵"
7868
+ },
7869
+ {
7870
+ id: "dog",
7871
+ name: "Dog",
7872
+ icon: "🐕"
7873
+ },
7874
+ {
7875
+ id: "cat",
7876
+ name: "Cat",
7877
+ icon: "🐈"
7878
+ },
7879
+ {
7880
+ id: "bird",
7881
+ name: "Bird",
7882
+ icon: "🐦"
7883
+ },
7884
+ {
7885
+ id: "animal",
7886
+ name: "Animal (other)",
7887
+ icon: "🐾"
7888
+ },
7889
+ {
7890
+ id: "alarm",
7891
+ name: "Alarm / Siren",
7892
+ icon: "🚨"
7893
+ },
7894
+ {
7895
+ id: "doorbell",
7896
+ name: "Doorbell / Knock",
7897
+ icon: "🔔"
7898
+ },
7899
+ {
7900
+ id: "glass_breaking",
7901
+ name: "Glass Breaking",
7902
+ icon: "💥"
7903
+ },
7904
+ {
7905
+ id: "gunshot",
7906
+ name: "Gunshot / Explosion",
7907
+ icon: "💣"
7908
+ },
7909
+ {
7910
+ id: "vehicle",
7911
+ name: "Vehicle",
7912
+ icon: "🚗"
7913
+ },
7914
+ {
7915
+ id: "siren",
7916
+ name: "Emergency Siren",
7917
+ icon: "🚑"
7918
+ },
7919
+ {
7920
+ id: "fire",
7921
+ name: "Fire / Smoke",
7922
+ icon: "🔥"
7923
+ },
7924
+ {
7925
+ id: "water",
7926
+ name: "Water",
7927
+ icon: "💧"
7928
+ },
7929
+ {
7930
+ id: "wind",
7931
+ name: "Wind / Weather",
7932
+ icon: "🌬️"
7933
+ },
7934
+ {
7935
+ id: "door",
7936
+ name: "Door",
7937
+ icon: "🚪"
7938
+ },
7939
+ {
7940
+ id: "footsteps",
7941
+ name: "Footsteps",
7942
+ icon: "👣"
7943
+ },
7944
+ {
7945
+ id: "crowd",
7946
+ name: "Crowd / Chatter",
7947
+ icon: "👥"
7948
+ },
7949
+ {
7950
+ id: "telephone",
7951
+ name: "Telephone",
7952
+ icon: "📞"
7953
+ },
7954
+ {
7955
+ id: "engine",
7956
+ name: "Engine / Motor",
7957
+ icon: "⚙️"
7958
+ },
7959
+ {
7960
+ id: "tools",
7961
+ name: "Tools / Construction",
7962
+ icon: "🔨"
7963
+ },
7964
+ {
7965
+ id: "silence",
7966
+ name: "Silence",
7967
+ icon: "🤫"
7968
+ }
7969
+ ];
7760
7970
  var YAMNET_TO_MACRO = {
7761
7971
  mapping: {
7762
7972
  Speech: "speech",
@@ -8023,6 +8233,125 @@ var _macroLookup = /* @__PURE__ */ new Map();
8023
8233
  for (const [k, v] of Object.entries(YAMNET_TO_MACRO.mapping)) _macroLookup.set(k.toLowerCase(), v);
8024
8234
  for (const [k, v] of Object.entries(APPLE_SA_TO_MACRO.mapping)) _macroLookup.set(k.toLowerCase(), v);
8025
8235
  /**
8236
+ * Unified event-kind taxonomy — THE single source of truth for
8237
+ * `kind → { parentKind, category, level, color, iconId, labelKey, label,
8238
+ * icon }`.
8239
+ *
8240
+ * This dictionary folds together what used to be scattered across four
8241
+ * copies:
8242
+ * - `capabilities/sensor-event-kinds.ts` (sensor cap colors)
8243
+ * - `addon-post-analysis/.../services/event-kinds.ts`
8244
+ * (MOTION/PERSON/VEHICLE… _COLOR constants)
8245
+ * - `ui-library/composites/detection-colors.ts` (CLASS_COLORS)
8246
+ * - `addon-post-analysis/shared/frame/box-drawer.ts` (DEFAULT_COLOR)
8247
+ * - the COCO / audio class maps (macro ↔ sub relationships)
8248
+ *
8249
+ * The DATA (serializable — color/iconId/labelKey/parentKind) lives here in
8250
+ * `@camstack/types`. The UI-side mapping `iconId → lucide component` and
8251
+ * `labelKey → t()` lives in `@camstack/ui-library`. UIs never hardcode a
8252
+ * color or an icon: they read this dictionary (server descriptors carry the
8253
+ * fields inline; the client resolves color/icon/label from `iconId`/`kind`).
8254
+ *
8255
+ * Two levels only (v1 YAGNI): macro → sub. `person` is a leaf macro.
8256
+ */
8257
+ var TAXONOMY_COLORS = {
8258
+ motion: "#f59e0b",
8259
+ audio: "#06b6d4",
8260
+ person: "#22c55e",
8261
+ vehicle: "#3b82f6",
8262
+ animal: "#f97316",
8263
+ package: "#a855f7",
8264
+ sensor: "#8b5cf6",
8265
+ control: "#10b981",
8266
+ genericDetection: "#64748b"
8267
+ };
8268
+ var DETECTION_SUB_COLORS = {
8269
+ car: "#f59e0b",
8270
+ truck: "#d97706",
8271
+ bus: "#b45309",
8272
+ motorcycle: "#eab308",
8273
+ bicycle: "#ca8a04",
8274
+ airplane: "#60a5fa",
8275
+ boat: "#2563eb",
8276
+ train: "#1d4ed8",
8277
+ bird: "#14b8a6",
8278
+ dog: "#84cc16",
8279
+ cat: "#f97316",
8280
+ horse: "#a16207",
8281
+ sheep: "#a3a3a3",
8282
+ cow: "#78716c",
8283
+ elephant: "#6b7280",
8284
+ bear: "#7c2d12",
8285
+ zebra: "#404040",
8286
+ giraffe: "#d4a373"
8287
+ };
8288
+ function titleCase(id) {
8289
+ return id.split(/[-_ ]/).filter((p) => p.length > 0).map((p) => p.charAt(0).toUpperCase() + p.slice(1)).join(" ");
8290
+ }
8291
+ var entries = /* @__PURE__ */ new Map();
8292
+ function macro(kind, category, color, iconId, label) {
8293
+ entries.set(kind, {
8294
+ kind,
8295
+ parentKind: null,
8296
+ level: "macro",
8297
+ category,
8298
+ color,
8299
+ iconId,
8300
+ labelKey: `eventKind.${kind}`,
8301
+ label
8302
+ });
8303
+ }
8304
+ function sub(kind, parentKind, category, color, iconId, label) {
8305
+ entries.set(kind, {
8306
+ kind,
8307
+ parentKind,
8308
+ level: "sub",
8309
+ category,
8310
+ color,
8311
+ iconId,
8312
+ labelKey: `eventKind.${kind}`,
8313
+ label
8314
+ });
8315
+ }
8316
+ macro("motion", "motion", TAXONOMY_COLORS.motion, "motion", "Motion");
8317
+ macro("audio", "audio", TAXONOMY_COLORS.audio, "audio", "Audio");
8318
+ macro("person", "detection", TAXONOMY_COLORS.person, "person", "Person");
8319
+ macro("vehicle", "detection", TAXONOMY_COLORS.vehicle, "vehicle", "Vehicle");
8320
+ macro("animal", "detection", TAXONOMY_COLORS.animal, "animal", "Animal");
8321
+ macro("package", "package", TAXONOMY_COLORS.package, "package", "Package");
8322
+ macro("sensor", "sensor", TAXONOMY_COLORS.sensor, "sensor", "Sensor");
8323
+ macro("control", "control", TAXONOMY_COLORS.control, "control", "Control");
8324
+ for (const [cocoClass, macroClass] of Object.entries(COCO_TO_MACRO.mapping)) {
8325
+ if (macroClass !== "vehicle" && macroClass !== "animal") continue;
8326
+ if (entries.has(cocoClass)) continue;
8327
+ sub(cocoClass, macroClass, "detection", DETECTION_SUB_COLORS[cocoClass] ?? TAXONOMY_COLORS.genericDetection, cocoClass, titleCase(cocoClass));
8328
+ }
8329
+ sub("package-delivered", "package", "package", TAXONOMY_COLORS.package, "package", "Package delivered");
8330
+ sub("package-picked-up", "package", "package", TAXONOMY_COLORS.package, "package", "Package picked up");
8331
+ sub("contact", "sensor", "sensor", "#f59e0b", "door", "Contact");
8332
+ sub("motion-sensor", "sensor", "sensor", TAXONOMY_COLORS.sensor, "pir", "Motion sensor");
8333
+ sub("smoke", "sensor", "sensor", "#ef4444", "smoke", "Smoke");
8334
+ sub("flood", "sensor", "sensor", "#3b82f6", "water", "Water leak");
8335
+ sub("gas", "sensor", "sensor", "#ef4444", "gas", "Gas");
8336
+ sub("carbon-monoxide", "sensor", "sensor", "#dc2626", "smoke", "Carbon monoxide");
8337
+ sub("vibration", "sensor", "sensor", "#eab308", "vibration", "Vibration");
8338
+ sub("tamper", "sensor", "sensor", "#f97316", "tamper", "Tamper");
8339
+ sub("presence", "sensor", "sensor", "#22c55e", "presence", "Presence");
8340
+ sub("enum-sensor", "sensor", "sensor", TAXONOMY_COLORS.sensor, "generic", "Sensor state");
8341
+ sub("device-event", "sensor", "sensor", "#10b981", "button", "Device event");
8342
+ sub("lock", "control", "control", "#0ea5e9", "lock", "Lock");
8343
+ sub("switch", "control", "control", TAXONOMY_COLORS.control, "switch", "Switch");
8344
+ sub("siren", "control", "control", "#dc2626", "siren", "Siren");
8345
+ sub("button", "control", "control", "#10b981", "button", "Button");
8346
+ sub("doorbell", "control", "control", "#a855f7", "doorbell", "Doorbell");
8347
+ for (const l of AUDIO_MACRO_LABELS) {
8348
+ const kind = `audio-${l.id}`;
8349
+ if (entries.has(kind)) continue;
8350
+ sub(kind, "audio", "audio", TAXONOMY_COLORS.audio, kind, l.name);
8351
+ }
8352
+ /** The complete taxonomy dictionary, keyed by kind. */
8353
+ var EVENT_TAXONOMY = Object.freeze(Object.fromEntries(entries));
8354
+ /**
8026
8355
  * Error types for the safe expression engine. Two distinct classes so callers
8027
8356
  * can tell a compile-time (grammar) failure from a runtime (evaluation)
8028
8357
  * failure — both are non-fatal to the host: read paths degrade to "skip link".
@@ -14977,9 +15306,10 @@ var DEVICE_LOCAL_STATE_CAPS = {
14977
15306
  * `package` backs the package-drop detector — a package zone is a
14978
15307
  * `ZoneRule` on the `'package'` stage referencing drawn polygons
14979
15308
  * (see docs/superpowers/specs/2026-07-17-package-zones-design.md §3.1).
14980
- * The orchestrator provider that writes this stage lands in a later
14981
- * slice; until then the mirror carries only `{motion, detection}` and
14982
- * consumers read `package` as absent (treat as `[]`).
15309
+ * The orchestrator provider writes this stage as a first-class slice
15310
+ * (Phase 4): every mutation mirrors the full `{motion, detection,
15311
+ * package}` shape, so consumers read the current package rules directly
15312
+ * off `device.state.zoneRules.value.package`.
14983
15313
  */
14984
15314
  runtimeState: object({
14985
15315
  motion: array(ZoneRuleSchema).readonly(),
@@ -19039,17 +19369,30 @@ var EventKindCategorySchema = _enum([
19039
19369
  "audio",
19040
19370
  "detection",
19041
19371
  "sensor",
19372
+ "control",
19042
19373
  "custom",
19043
19374
  "package"
19044
19375
  ]);
19376
+ /** Taxonomy level — macro (timeline lane) vs sub (events-page leaf). */
19377
+ var EventKindLevelSchema = _enum(["macro", "sub"]);
19045
19378
  var EventKindDescriptorSchema = object({
19046
- /** Stable kind id (e.g. 'motion', 'person', 'contact'). */
19379
+ /** Stable kind id (e.g. 'motion', 'vehicle', 'car', 'lock'). */
19047
19380
  kind: string(),
19381
+ /** i18n key resolved on the UI side; `label` is the English fallback. */
19382
+ labelKey: string(),
19383
+ /** English fallback label (kept for clients that don't translate). */
19048
19384
  label: string(),
19049
19385
  /** Hex color for timeline/legend rendering. */
19050
19386
  color: string(),
19387
+ /** Dictionary id → lucide component on the UI side. */
19388
+ iconId: string(),
19389
+ /** Legacy closed-vocab glyph — fallback for `iconId`. */
19051
19390
  icon: EventKindIconSchema,
19052
19391
  category: EventKindCategorySchema,
19392
+ /** Macro parent for this kind ('car' → 'vehicle'); null for a macro. */
19393
+ parentKind: string().nullable(),
19394
+ /** Derived from `parentKind`, explicit for the client tree. */
19395
+ level: EventKindLevelSchema,
19053
19396
  /** Which cap + device contributes this kind. For built-ins the camera
19054
19397
  * itself; for sensor kinds the LINKED source device. */
19055
19398
  source: object({
@@ -19122,11 +19465,21 @@ var TrackAudioLabelSchema = object({
19122
19465
  firstAt: number(),
19123
19466
  lastAt: number()
19124
19467
  });
19468
+ /**
19469
+ * How a track was produced. `pipeline` (default / absent) = the spatial
19470
+ * detection+tracking pipeline. `sensor` = a SYNTHETIC track projected from a
19471
+ * linked sensor/control state change (no positions; carries a snapshot). The
19472
+ * spatial subsystems (tracker association, occupancy count, re-id/embedding,
19473
+ * resurrection) MUST skip `sensor` tracks — they have no bbox trajectory.
19474
+ */
19475
+ var TrackSourceSchema = _enum(["pipeline", "sensor"]);
19125
19476
  var TrackSchema = object({
19126
19477
  trackId: string(),
19127
19478
  deviceId: number(),
19128
19479
  className: string(),
19129
19480
  label: string().optional(),
19481
+ /** Track provenance. Absent ⇒ `pipeline` (legacy rows). */
19482
+ source: TrackSourceSchema.optional(),
19130
19483
  firstSeen: number(),
19131
19484
  lastSeen: number(),
19132
19485
  /** Frame-rate position history (subject to maxPositionHistory cap). */
@@ -19381,6 +19734,26 @@ var TrackCascadeCountsSchema = object({
19381
19734
  /** Per-track CLIP search vectors removed (best-effort). */
19382
19735
  embeddings: number().int()
19383
19736
  });
19737
+ /** Event-store footprint for one camera. */
19738
+ var EventStoreDeviceFootprintSchema = object({
19739
+ deviceId: number(),
19740
+ /** Persisted event rows (motion + object + audio) for the camera. */
19741
+ rows: number().int(),
19742
+ /** Event-owned media bytes on disk for the camera. */
19743
+ bytes: number().int()
19744
+ });
19745
+ /** Aggregate event-store footprint: global totals + per-camera breakdown. */
19746
+ var EventStoreFootprintSchema = object({
19747
+ totalRows: number().int(),
19748
+ totalBytes: number().int(),
19749
+ devices: array(EventStoreDeviceFootprintSchema).readonly()
19750
+ });
19751
+ /** Per-kind counts returned by the event-prune / device-delete mutations. */
19752
+ var EventPruneCountsSchema = object({
19753
+ motion: number().int(),
19754
+ object: number().int(),
19755
+ audio: number().int()
19756
+ });
19384
19757
  DeviceType.Camera, method(object({ deviceId: number() }), array(TrackSchema).readonly()), method(object({
19385
19758
  deviceId: number(),
19386
19759
  trackId: string()
@@ -19444,6 +19817,21 @@ DeviceType.Camera, method(object({ deviceId: number() }), array(TrackSchema).rea
19444
19817
  }), {
19445
19818
  kind: "mutation",
19446
19819
  auth: "admin"
19820
+ }), method(object({}), EventStoreFootprintSchema, {
19821
+ kind: "query",
19822
+ auth: "admin"
19823
+ }), method(object({
19824
+ olderThanMs: number(),
19825
+ reason: OpsLogReasonSchema.optional()
19826
+ }), EventPruneCountsSchema, {
19827
+ kind: "mutation",
19828
+ auth: "admin"
19829
+ }), method(object({ deviceId: number() }), EventPruneCountsSchema, {
19830
+ kind: "mutation",
19831
+ auth: "admin"
19832
+ }), method(OpsLogQueryInputSchema, array(OpsLogEntrySchema).readonly(), {
19833
+ kind: "query",
19834
+ auth: "admin"
19447
19835
  }), method(object({
19448
19836
  eventId: string(),
19449
19837
  kind: MediaFileKindEnum.optional()
@@ -19468,6 +19856,76 @@ DeviceType.Camera, method(object({ deviceId: number() }), array(TrackSchema).rea
19468
19856
  eventId: string(),
19469
19857
  timestamp: number()
19470
19858
  });
19859
+ /**
19860
+ * Cap → event-kind mapping for the SENSOR / CONTROL cap families — the table
19861
+ * `pipeline-analytics.listEventKinds` uses to turn a linked device's bound
19862
+ * caps into per-camera event-kind descriptors.
19863
+ *
19864
+ * The descriptor DATA (color / iconId / labelKey / parentKind / category)
19865
+ * is NOT duplicated here — every entry is derived from the single
19866
+ * `EVENT_TAXONOMY` dictionary (`catalogs/event-taxonomy.ts`). This file owns
19867
+ * ONLY the cap-name → taxonomy-kind mapping. Adding a new eventful sensor /
19868
+ * control cap means adding one line here (and a taxonomy entry); the anti-
19869
+ * drift guard `scripts/check-event-kind-coverage.ts` fails the build if an
19870
+ * eventful cap is missing.
19871
+ */
19872
+ /** Map a taxonomy `iconId` onto the legacy closed `EventKindIcon` enum. */
19873
+ var LEGACY_ICON = {
19874
+ motion: "motion",
19875
+ audio: "audio",
19876
+ person: "person",
19877
+ vehicle: "vehicle",
19878
+ animal: "animal",
19879
+ package: "package",
19880
+ door: "door",
19881
+ pir: "pir",
19882
+ smoke: "smoke",
19883
+ water: "water",
19884
+ button: "button",
19885
+ generic: "generic",
19886
+ gas: "smoke",
19887
+ vibration: "generic",
19888
+ tamper: "generic",
19889
+ presence: "person",
19890
+ lock: "generic",
19891
+ siren: "generic",
19892
+ switch: "generic",
19893
+ doorbell: "button"
19894
+ };
19895
+ function legacyIcon(iconId) {
19896
+ return LEGACY_ICON[iconId] ?? "generic";
19897
+ }
19898
+ /**
19899
+ * Cap name → taxonomy kind id. Covers EVERY eventful sensor / control cap.
19900
+ * The anti-drift guard cross-checks this against the eventful caps declared
19901
+ * in `packages/types/src/capabilities/*.cap.ts`.
19902
+ */
19903
+ var CAP_TO_KIND = {
19904
+ contact: "contact",
19905
+ motion: "motion-sensor",
19906
+ smoke: "smoke",
19907
+ flood: "flood",
19908
+ gas: "gas",
19909
+ "carbon-monoxide": "carbon-monoxide",
19910
+ vibration: "vibration",
19911
+ tamper: "tamper",
19912
+ presence: "presence",
19913
+ "enum-sensor": "enum-sensor",
19914
+ "event-emitter": "device-event",
19915
+ "lock-control": "lock",
19916
+ switch: "switch",
19917
+ button: "button",
19918
+ doorbell: "doorbell"
19919
+ };
19920
+ function buildDescriptor(capName, kind) {
19921
+ const t = EVENT_TAXONOMY[kind];
19922
+ if (t === void 0) throw new Error(`EVENT_KIND_BY_CAP: cap '${capName}' maps to unknown taxonomy kind '${kind}'`);
19923
+ return {
19924
+ ...t,
19925
+ icon: legacyIcon(t.iconId)
19926
+ };
19927
+ }
19928
+ Object.freeze(Object.fromEntries(Object.entries(CAP_TO_KIND).map(([capName, kind]) => [capName, buildDescriptor(capName, kind)])));
19471
19929
  var CameraPipelineConfigSchema = object({
19472
19930
  engine: PipelineEngineChoiceSchema.optional(),
19473
19931
  steps: array(PipelineStepInputSchema).readonly(),
@@ -22694,13 +23152,29 @@ method(object({
22694
23152
  }), method(object({ deviceId: number() }), RecordingStatusSchema, {
22695
23153
  kind: "mutation",
22696
23154
  auth: "admin"
22697
- }), method(object({ deviceId: number() }), object({
23155
+ }), method(object({
23156
+ deviceId: number(),
23157
+ reason: OpsLogReasonSchema.optional()
23158
+ }), object({
22698
23159
  floorMs: number().nullable(),
22699
23160
  deletedBuckets: number().int(),
22700
23161
  reclaimedBytes: number().int()
22701
23162
  }), {
22702
23163
  kind: "mutation",
22703
23164
  auth: "admin"
23165
+ }), method(object({
23166
+ deviceId: number(),
23167
+ fromMs: number().optional(),
23168
+ toMs: number().optional()
23169
+ }), object({
23170
+ deletedBuckets: number().int(),
23171
+ reclaimedBytes: number().int()
23172
+ }), {
23173
+ kind: "mutation",
23174
+ auth: "admin"
23175
+ }), method(OpsLogQueryInputSchema, array(OpsLogEntrySchema).readonly(), {
23176
+ kind: "query",
23177
+ auth: "admin"
22704
23178
  });
22705
23179
  /**
22706
23180
  * `recordingExport` cap — render a footage time range into a single downloadable
@@ -25822,6 +26296,12 @@ Object.freeze({
25822
26296
  addonId: null,
25823
26297
  access: "delete"
25824
26298
  },
26299
+ "pipelineAnalytics.deleteDeviceEvents": {
26300
+ capName: "pipeline-analytics",
26301
+ capScope: "device",
26302
+ addonId: null,
26303
+ access: "delete"
26304
+ },
25825
26305
  "pipelineAnalytics.deleteTracks": {
25826
26306
  capName: "pipeline-analytics",
25827
26307
  capScope: "device",
@@ -25852,6 +26332,12 @@ Object.freeze({
25852
26332
  addonId: null,
25853
26333
  access: "view"
25854
26334
  },
26335
+ "pipelineAnalytics.getEventStoreFootprint": {
26336
+ capName: "pipeline-analytics",
26337
+ capScope: "device",
26338
+ addonId: null,
26339
+ access: "view"
26340
+ },
25855
26341
  "pipelineAnalytics.getKeyEvents": {
25856
26342
  capName: "pipeline-analytics",
25857
26343
  capScope: "device",
@@ -25894,6 +26380,12 @@ Object.freeze({
25894
26380
  addonId: null,
25895
26381
  access: "view"
25896
26382
  },
26383
+ "pipelineAnalytics.listOpsLog": {
26384
+ capName: "pipeline-analytics",
26385
+ capScope: "device",
26386
+ addonId: null,
26387
+ access: "view"
26388
+ },
25897
26389
  "pipelineAnalytics.listRecentTracks": {
25898
26390
  capName: "pipeline-analytics",
25899
26391
  capScope: "device",
@@ -25906,6 +26398,12 @@ Object.freeze({
25906
26398
  addonId: null,
25907
26399
  access: "view"
25908
26400
  },
26401
+ "pipelineAnalytics.pruneEvents": {
26402
+ capName: "pipeline-analytics",
26403
+ capScope: "device",
26404
+ addonId: null,
26405
+ access: "create"
26406
+ },
25909
26407
  "pipelineAnalytics.pruneEventsBefore": {
25910
26408
  capName: "pipeline-analytics",
25911
26409
  capScope: "device",
@@ -26668,6 +27166,12 @@ Object.freeze({
26668
27166
  addonId: null,
26669
27167
  access: "create"
26670
27168
  },
27169
+ "recording.deleteFootprint": {
27170
+ capName: "recording",
27171
+ capScope: "system",
27172
+ addonId: null,
27173
+ access: "delete"
27174
+ },
26671
27175
  "recording.getAvailability": {
26672
27176
  capName: "recording",
26673
27177
  capScope: "system",
@@ -26698,6 +27202,12 @@ Object.freeze({
26698
27202
  addonId: null,
26699
27203
  access: "view"
26700
27204
  },
27205
+ "recording.listOpsLog": {
27206
+ capName: "recording",
27207
+ capScope: "system",
27208
+ addonId: null,
27209
+ access: "view"
27210
+ },
26701
27211
  "recording.locateSegment": {
26702
27212
  capName: "recording",
26703
27213
  capScope: "system",
package/dist/addon.mjs CHANGED
@@ -7514,6 +7514,62 @@ var RecordingConfigSchema = object({
7514
7514
  scrubThumbnails: ScrubThumbnailPresetSchema.optional()
7515
7515
  });
7516
7516
  /**
7517
+ * Ops-log — the durable, append-only operations audit shared by the
7518
+ * recordings and events management surfaces.
7519
+ *
7520
+ * ONE row shape is reused for both domains so a single "Activity" view can
7521
+ * merge the recorder's DurableState ring (recordings ops-log) and the
7522
+ * pipeline-analytics SQLite collection (events ops-log). Each row records a
7523
+ * management operation, WHY it ran (reason), and its measurable effect
7524
+ * (itemsAffected + bytesReclaimed). Writes are best-effort — a failed log must
7525
+ * never fail the operation it records.
7526
+ */
7527
+ /** Which management domain the operation belongs to. */
7528
+ var OpsLogDomainSchema = _enum(["recording", "events"]);
7529
+ /** The kind of management operation performed. */
7530
+ var OpsLogOpSchema = _enum([
7531
+ "prune",
7532
+ "manual-delete",
7533
+ "rescan",
7534
+ "retention-run"
7535
+ ]);
7536
+ /** Why the operation ran. */
7537
+ var OpsLogReasonSchema = _enum([
7538
+ "retention",
7539
+ "quota",
7540
+ "manual",
7541
+ "operator"
7542
+ ]);
7543
+ /** One audit row, shared verbatim by both domains. */
7544
+ var OpsLogEntrySchema = object({
7545
+ /** Unique row id. */
7546
+ id: string(),
7547
+ /** Epoch ms the operation completed. */
7548
+ at: number(),
7549
+ domain: OpsLogDomainSchema,
7550
+ op: OpsLogOpSchema,
7551
+ reason: OpsLogReasonSchema,
7552
+ /** The camera the op targeted; null for a cluster/global op. */
7553
+ deviceId: number().nullable(),
7554
+ /** Node that performed the op (the log carries nodeId — no cross-node aggregation). */
7555
+ nodeId: string(),
7556
+ /** Buckets / rows deleted (op-specific unit). */
7557
+ itemsAffected: number(),
7558
+ /** Bytes reclaimed by the op (0 when not measurable). */
7559
+ bytesReclaimed: number(),
7560
+ /** Free-text detail (e.g. "floor moved to <ts>"); null when none. */
7561
+ detail: string().nullable(),
7562
+ /** Who/what triggered the op. */
7563
+ actor: string()
7564
+ });
7565
+ /** Shared query input for the per-domain `listOpsLog` cap methods. */
7566
+ var OpsLogQueryInputSchema = object({
7567
+ /** Restrict to a single camera; omit for every row. */
7568
+ deviceId: number().optional(),
7569
+ /** Max rows returned, newest-first. */
7570
+ limit: number().int().min(1).max(1e3).optional()
7571
+ });
7572
+ /**
7517
7573
  * `StorageLocationType` — an addon-declared id that identifies the *kind* of
7518
7574
  * storage a location serves. Defined here (not in `capabilities/storage.cap.ts`)
7519
7575
  * so the persisted record schema and the consumer-facing cap can both consume it
@@ -7757,6 +7813,160 @@ var EncodeProfileSchema = object({
7757
7813
  */
7758
7814
  outputArgs: array(string()).optional()
7759
7815
  });
7816
+ var COCO_TO_MACRO = {
7817
+ mapping: {
7818
+ person: "person",
7819
+ bicycle: "vehicle",
7820
+ car: "vehicle",
7821
+ motorcycle: "vehicle",
7822
+ airplane: "vehicle",
7823
+ bus: "vehicle",
7824
+ train: "vehicle",
7825
+ truck: "vehicle",
7826
+ boat: "vehicle",
7827
+ bird: "animal",
7828
+ cat: "animal",
7829
+ dog: "animal",
7830
+ horse: "animal",
7831
+ sheep: "animal",
7832
+ cow: "animal",
7833
+ elephant: "animal",
7834
+ bear: "animal",
7835
+ zebra: "animal",
7836
+ giraffe: "animal",
7837
+ suitcase: "package",
7838
+ backpack: "package",
7839
+ handbag: "package"
7840
+ },
7841
+ preserveOriginal: false
7842
+ };
7843
+ var AUDIO_MACRO_LABELS = [
7844
+ {
7845
+ id: "speech",
7846
+ name: "Speech",
7847
+ icon: "🗣️"
7848
+ },
7849
+ {
7850
+ id: "scream",
7851
+ name: "Scream / Shout",
7852
+ icon: "😱"
7853
+ },
7854
+ {
7855
+ id: "crying",
7856
+ name: "Crying / Baby",
7857
+ icon: "😢"
7858
+ },
7859
+ {
7860
+ id: "laughter",
7861
+ name: "Laughter",
7862
+ icon: "😂"
7863
+ },
7864
+ {
7865
+ id: "music",
7866
+ name: "Music",
7867
+ icon: "🎵"
7868
+ },
7869
+ {
7870
+ id: "dog",
7871
+ name: "Dog",
7872
+ icon: "🐕"
7873
+ },
7874
+ {
7875
+ id: "cat",
7876
+ name: "Cat",
7877
+ icon: "🐈"
7878
+ },
7879
+ {
7880
+ id: "bird",
7881
+ name: "Bird",
7882
+ icon: "🐦"
7883
+ },
7884
+ {
7885
+ id: "animal",
7886
+ name: "Animal (other)",
7887
+ icon: "🐾"
7888
+ },
7889
+ {
7890
+ id: "alarm",
7891
+ name: "Alarm / Siren",
7892
+ icon: "🚨"
7893
+ },
7894
+ {
7895
+ id: "doorbell",
7896
+ name: "Doorbell / Knock",
7897
+ icon: "🔔"
7898
+ },
7899
+ {
7900
+ id: "glass_breaking",
7901
+ name: "Glass Breaking",
7902
+ icon: "💥"
7903
+ },
7904
+ {
7905
+ id: "gunshot",
7906
+ name: "Gunshot / Explosion",
7907
+ icon: "💣"
7908
+ },
7909
+ {
7910
+ id: "vehicle",
7911
+ name: "Vehicle",
7912
+ icon: "🚗"
7913
+ },
7914
+ {
7915
+ id: "siren",
7916
+ name: "Emergency Siren",
7917
+ icon: "🚑"
7918
+ },
7919
+ {
7920
+ id: "fire",
7921
+ name: "Fire / Smoke",
7922
+ icon: "🔥"
7923
+ },
7924
+ {
7925
+ id: "water",
7926
+ name: "Water",
7927
+ icon: "💧"
7928
+ },
7929
+ {
7930
+ id: "wind",
7931
+ name: "Wind / Weather",
7932
+ icon: "🌬️"
7933
+ },
7934
+ {
7935
+ id: "door",
7936
+ name: "Door",
7937
+ icon: "🚪"
7938
+ },
7939
+ {
7940
+ id: "footsteps",
7941
+ name: "Footsteps",
7942
+ icon: "👣"
7943
+ },
7944
+ {
7945
+ id: "crowd",
7946
+ name: "Crowd / Chatter",
7947
+ icon: "👥"
7948
+ },
7949
+ {
7950
+ id: "telephone",
7951
+ name: "Telephone",
7952
+ icon: "📞"
7953
+ },
7954
+ {
7955
+ id: "engine",
7956
+ name: "Engine / Motor",
7957
+ icon: "⚙️"
7958
+ },
7959
+ {
7960
+ id: "tools",
7961
+ name: "Tools / Construction",
7962
+ icon: "🔨"
7963
+ },
7964
+ {
7965
+ id: "silence",
7966
+ name: "Silence",
7967
+ icon: "🤫"
7968
+ }
7969
+ ];
7760
7970
  var YAMNET_TO_MACRO = {
7761
7971
  mapping: {
7762
7972
  Speech: "speech",
@@ -8023,6 +8233,125 @@ var _macroLookup = /* @__PURE__ */ new Map();
8023
8233
  for (const [k, v] of Object.entries(YAMNET_TO_MACRO.mapping)) _macroLookup.set(k.toLowerCase(), v);
8024
8234
  for (const [k, v] of Object.entries(APPLE_SA_TO_MACRO.mapping)) _macroLookup.set(k.toLowerCase(), v);
8025
8235
  /**
8236
+ * Unified event-kind taxonomy — THE single source of truth for
8237
+ * `kind → { parentKind, category, level, color, iconId, labelKey, label,
8238
+ * icon }`.
8239
+ *
8240
+ * This dictionary folds together what used to be scattered across four
8241
+ * copies:
8242
+ * - `capabilities/sensor-event-kinds.ts` (sensor cap colors)
8243
+ * - `addon-post-analysis/.../services/event-kinds.ts`
8244
+ * (MOTION/PERSON/VEHICLE… _COLOR constants)
8245
+ * - `ui-library/composites/detection-colors.ts` (CLASS_COLORS)
8246
+ * - `addon-post-analysis/shared/frame/box-drawer.ts` (DEFAULT_COLOR)
8247
+ * - the COCO / audio class maps (macro ↔ sub relationships)
8248
+ *
8249
+ * The DATA (serializable — color/iconId/labelKey/parentKind) lives here in
8250
+ * `@camstack/types`. The UI-side mapping `iconId → lucide component` and
8251
+ * `labelKey → t()` lives in `@camstack/ui-library`. UIs never hardcode a
8252
+ * color or an icon: they read this dictionary (server descriptors carry the
8253
+ * fields inline; the client resolves color/icon/label from `iconId`/`kind`).
8254
+ *
8255
+ * Two levels only (v1 YAGNI): macro → sub. `person` is a leaf macro.
8256
+ */
8257
+ var TAXONOMY_COLORS = {
8258
+ motion: "#f59e0b",
8259
+ audio: "#06b6d4",
8260
+ person: "#22c55e",
8261
+ vehicle: "#3b82f6",
8262
+ animal: "#f97316",
8263
+ package: "#a855f7",
8264
+ sensor: "#8b5cf6",
8265
+ control: "#10b981",
8266
+ genericDetection: "#64748b"
8267
+ };
8268
+ var DETECTION_SUB_COLORS = {
8269
+ car: "#f59e0b",
8270
+ truck: "#d97706",
8271
+ bus: "#b45309",
8272
+ motorcycle: "#eab308",
8273
+ bicycle: "#ca8a04",
8274
+ airplane: "#60a5fa",
8275
+ boat: "#2563eb",
8276
+ train: "#1d4ed8",
8277
+ bird: "#14b8a6",
8278
+ dog: "#84cc16",
8279
+ cat: "#f97316",
8280
+ horse: "#a16207",
8281
+ sheep: "#a3a3a3",
8282
+ cow: "#78716c",
8283
+ elephant: "#6b7280",
8284
+ bear: "#7c2d12",
8285
+ zebra: "#404040",
8286
+ giraffe: "#d4a373"
8287
+ };
8288
+ function titleCase(id) {
8289
+ return id.split(/[-_ ]/).filter((p) => p.length > 0).map((p) => p.charAt(0).toUpperCase() + p.slice(1)).join(" ");
8290
+ }
8291
+ var entries = /* @__PURE__ */ new Map();
8292
+ function macro(kind, category, color, iconId, label) {
8293
+ entries.set(kind, {
8294
+ kind,
8295
+ parentKind: null,
8296
+ level: "macro",
8297
+ category,
8298
+ color,
8299
+ iconId,
8300
+ labelKey: `eventKind.${kind}`,
8301
+ label
8302
+ });
8303
+ }
8304
+ function sub(kind, parentKind, category, color, iconId, label) {
8305
+ entries.set(kind, {
8306
+ kind,
8307
+ parentKind,
8308
+ level: "sub",
8309
+ category,
8310
+ color,
8311
+ iconId,
8312
+ labelKey: `eventKind.${kind}`,
8313
+ label
8314
+ });
8315
+ }
8316
+ macro("motion", "motion", TAXONOMY_COLORS.motion, "motion", "Motion");
8317
+ macro("audio", "audio", TAXONOMY_COLORS.audio, "audio", "Audio");
8318
+ macro("person", "detection", TAXONOMY_COLORS.person, "person", "Person");
8319
+ macro("vehicle", "detection", TAXONOMY_COLORS.vehicle, "vehicle", "Vehicle");
8320
+ macro("animal", "detection", TAXONOMY_COLORS.animal, "animal", "Animal");
8321
+ macro("package", "package", TAXONOMY_COLORS.package, "package", "Package");
8322
+ macro("sensor", "sensor", TAXONOMY_COLORS.sensor, "sensor", "Sensor");
8323
+ macro("control", "control", TAXONOMY_COLORS.control, "control", "Control");
8324
+ for (const [cocoClass, macroClass] of Object.entries(COCO_TO_MACRO.mapping)) {
8325
+ if (macroClass !== "vehicle" && macroClass !== "animal") continue;
8326
+ if (entries.has(cocoClass)) continue;
8327
+ sub(cocoClass, macroClass, "detection", DETECTION_SUB_COLORS[cocoClass] ?? TAXONOMY_COLORS.genericDetection, cocoClass, titleCase(cocoClass));
8328
+ }
8329
+ sub("package-delivered", "package", "package", TAXONOMY_COLORS.package, "package", "Package delivered");
8330
+ sub("package-picked-up", "package", "package", TAXONOMY_COLORS.package, "package", "Package picked up");
8331
+ sub("contact", "sensor", "sensor", "#f59e0b", "door", "Contact");
8332
+ sub("motion-sensor", "sensor", "sensor", TAXONOMY_COLORS.sensor, "pir", "Motion sensor");
8333
+ sub("smoke", "sensor", "sensor", "#ef4444", "smoke", "Smoke");
8334
+ sub("flood", "sensor", "sensor", "#3b82f6", "water", "Water leak");
8335
+ sub("gas", "sensor", "sensor", "#ef4444", "gas", "Gas");
8336
+ sub("carbon-monoxide", "sensor", "sensor", "#dc2626", "smoke", "Carbon monoxide");
8337
+ sub("vibration", "sensor", "sensor", "#eab308", "vibration", "Vibration");
8338
+ sub("tamper", "sensor", "sensor", "#f97316", "tamper", "Tamper");
8339
+ sub("presence", "sensor", "sensor", "#22c55e", "presence", "Presence");
8340
+ sub("enum-sensor", "sensor", "sensor", TAXONOMY_COLORS.sensor, "generic", "Sensor state");
8341
+ sub("device-event", "sensor", "sensor", "#10b981", "button", "Device event");
8342
+ sub("lock", "control", "control", "#0ea5e9", "lock", "Lock");
8343
+ sub("switch", "control", "control", TAXONOMY_COLORS.control, "switch", "Switch");
8344
+ sub("siren", "control", "control", "#dc2626", "siren", "Siren");
8345
+ sub("button", "control", "control", "#10b981", "button", "Button");
8346
+ sub("doorbell", "control", "control", "#a855f7", "doorbell", "Doorbell");
8347
+ for (const l of AUDIO_MACRO_LABELS) {
8348
+ const kind = `audio-${l.id}`;
8349
+ if (entries.has(kind)) continue;
8350
+ sub(kind, "audio", "audio", TAXONOMY_COLORS.audio, kind, l.name);
8351
+ }
8352
+ /** The complete taxonomy dictionary, keyed by kind. */
8353
+ var EVENT_TAXONOMY = Object.freeze(Object.fromEntries(entries));
8354
+ /**
8026
8355
  * Error types for the safe expression engine. Two distinct classes so callers
8027
8356
  * can tell a compile-time (grammar) failure from a runtime (evaluation)
8028
8357
  * failure — both are non-fatal to the host: read paths degrade to "skip link".
@@ -14977,9 +15306,10 @@ var DEVICE_LOCAL_STATE_CAPS = {
14977
15306
  * `package` backs the package-drop detector — a package zone is a
14978
15307
  * `ZoneRule` on the `'package'` stage referencing drawn polygons
14979
15308
  * (see docs/superpowers/specs/2026-07-17-package-zones-design.md §3.1).
14980
- * The orchestrator provider that writes this stage lands in a later
14981
- * slice; until then the mirror carries only `{motion, detection}` and
14982
- * consumers read `package` as absent (treat as `[]`).
15309
+ * The orchestrator provider writes this stage as a first-class slice
15310
+ * (Phase 4): every mutation mirrors the full `{motion, detection,
15311
+ * package}` shape, so consumers read the current package rules directly
15312
+ * off `device.state.zoneRules.value.package`.
14983
15313
  */
14984
15314
  runtimeState: object({
14985
15315
  motion: array(ZoneRuleSchema).readonly(),
@@ -19039,17 +19369,30 @@ var EventKindCategorySchema = _enum([
19039
19369
  "audio",
19040
19370
  "detection",
19041
19371
  "sensor",
19372
+ "control",
19042
19373
  "custom",
19043
19374
  "package"
19044
19375
  ]);
19376
+ /** Taxonomy level — macro (timeline lane) vs sub (events-page leaf). */
19377
+ var EventKindLevelSchema = _enum(["macro", "sub"]);
19045
19378
  var EventKindDescriptorSchema = object({
19046
- /** Stable kind id (e.g. 'motion', 'person', 'contact'). */
19379
+ /** Stable kind id (e.g. 'motion', 'vehicle', 'car', 'lock'). */
19047
19380
  kind: string(),
19381
+ /** i18n key resolved on the UI side; `label` is the English fallback. */
19382
+ labelKey: string(),
19383
+ /** English fallback label (kept for clients that don't translate). */
19048
19384
  label: string(),
19049
19385
  /** Hex color for timeline/legend rendering. */
19050
19386
  color: string(),
19387
+ /** Dictionary id → lucide component on the UI side. */
19388
+ iconId: string(),
19389
+ /** Legacy closed-vocab glyph — fallback for `iconId`. */
19051
19390
  icon: EventKindIconSchema,
19052
19391
  category: EventKindCategorySchema,
19392
+ /** Macro parent for this kind ('car' → 'vehicle'); null for a macro. */
19393
+ parentKind: string().nullable(),
19394
+ /** Derived from `parentKind`, explicit for the client tree. */
19395
+ level: EventKindLevelSchema,
19053
19396
  /** Which cap + device contributes this kind. For built-ins the camera
19054
19397
  * itself; for sensor kinds the LINKED source device. */
19055
19398
  source: object({
@@ -19122,11 +19465,21 @@ var TrackAudioLabelSchema = object({
19122
19465
  firstAt: number(),
19123
19466
  lastAt: number()
19124
19467
  });
19468
+ /**
19469
+ * How a track was produced. `pipeline` (default / absent) = the spatial
19470
+ * detection+tracking pipeline. `sensor` = a SYNTHETIC track projected from a
19471
+ * linked sensor/control state change (no positions; carries a snapshot). The
19472
+ * spatial subsystems (tracker association, occupancy count, re-id/embedding,
19473
+ * resurrection) MUST skip `sensor` tracks — they have no bbox trajectory.
19474
+ */
19475
+ var TrackSourceSchema = _enum(["pipeline", "sensor"]);
19125
19476
  var TrackSchema = object({
19126
19477
  trackId: string(),
19127
19478
  deviceId: number(),
19128
19479
  className: string(),
19129
19480
  label: string().optional(),
19481
+ /** Track provenance. Absent ⇒ `pipeline` (legacy rows). */
19482
+ source: TrackSourceSchema.optional(),
19130
19483
  firstSeen: number(),
19131
19484
  lastSeen: number(),
19132
19485
  /** Frame-rate position history (subject to maxPositionHistory cap). */
@@ -19381,6 +19734,26 @@ var TrackCascadeCountsSchema = object({
19381
19734
  /** Per-track CLIP search vectors removed (best-effort). */
19382
19735
  embeddings: number().int()
19383
19736
  });
19737
+ /** Event-store footprint for one camera. */
19738
+ var EventStoreDeviceFootprintSchema = object({
19739
+ deviceId: number(),
19740
+ /** Persisted event rows (motion + object + audio) for the camera. */
19741
+ rows: number().int(),
19742
+ /** Event-owned media bytes on disk for the camera. */
19743
+ bytes: number().int()
19744
+ });
19745
+ /** Aggregate event-store footprint: global totals + per-camera breakdown. */
19746
+ var EventStoreFootprintSchema = object({
19747
+ totalRows: number().int(),
19748
+ totalBytes: number().int(),
19749
+ devices: array(EventStoreDeviceFootprintSchema).readonly()
19750
+ });
19751
+ /** Per-kind counts returned by the event-prune / device-delete mutations. */
19752
+ var EventPruneCountsSchema = object({
19753
+ motion: number().int(),
19754
+ object: number().int(),
19755
+ audio: number().int()
19756
+ });
19384
19757
  DeviceType.Camera, method(object({ deviceId: number() }), array(TrackSchema).readonly()), method(object({
19385
19758
  deviceId: number(),
19386
19759
  trackId: string()
@@ -19444,6 +19817,21 @@ DeviceType.Camera, method(object({ deviceId: number() }), array(TrackSchema).rea
19444
19817
  }), {
19445
19818
  kind: "mutation",
19446
19819
  auth: "admin"
19820
+ }), method(object({}), EventStoreFootprintSchema, {
19821
+ kind: "query",
19822
+ auth: "admin"
19823
+ }), method(object({
19824
+ olderThanMs: number(),
19825
+ reason: OpsLogReasonSchema.optional()
19826
+ }), EventPruneCountsSchema, {
19827
+ kind: "mutation",
19828
+ auth: "admin"
19829
+ }), method(object({ deviceId: number() }), EventPruneCountsSchema, {
19830
+ kind: "mutation",
19831
+ auth: "admin"
19832
+ }), method(OpsLogQueryInputSchema, array(OpsLogEntrySchema).readonly(), {
19833
+ kind: "query",
19834
+ auth: "admin"
19447
19835
  }), method(object({
19448
19836
  eventId: string(),
19449
19837
  kind: MediaFileKindEnum.optional()
@@ -19468,6 +19856,76 @@ DeviceType.Camera, method(object({ deviceId: number() }), array(TrackSchema).rea
19468
19856
  eventId: string(),
19469
19857
  timestamp: number()
19470
19858
  });
19859
+ /**
19860
+ * Cap → event-kind mapping for the SENSOR / CONTROL cap families — the table
19861
+ * `pipeline-analytics.listEventKinds` uses to turn a linked device's bound
19862
+ * caps into per-camera event-kind descriptors.
19863
+ *
19864
+ * The descriptor DATA (color / iconId / labelKey / parentKind / category)
19865
+ * is NOT duplicated here — every entry is derived from the single
19866
+ * `EVENT_TAXONOMY` dictionary (`catalogs/event-taxonomy.ts`). This file owns
19867
+ * ONLY the cap-name → taxonomy-kind mapping. Adding a new eventful sensor /
19868
+ * control cap means adding one line here (and a taxonomy entry); the anti-
19869
+ * drift guard `scripts/check-event-kind-coverage.ts` fails the build if an
19870
+ * eventful cap is missing.
19871
+ */
19872
+ /** Map a taxonomy `iconId` onto the legacy closed `EventKindIcon` enum. */
19873
+ var LEGACY_ICON = {
19874
+ motion: "motion",
19875
+ audio: "audio",
19876
+ person: "person",
19877
+ vehicle: "vehicle",
19878
+ animal: "animal",
19879
+ package: "package",
19880
+ door: "door",
19881
+ pir: "pir",
19882
+ smoke: "smoke",
19883
+ water: "water",
19884
+ button: "button",
19885
+ generic: "generic",
19886
+ gas: "smoke",
19887
+ vibration: "generic",
19888
+ tamper: "generic",
19889
+ presence: "person",
19890
+ lock: "generic",
19891
+ siren: "generic",
19892
+ switch: "generic",
19893
+ doorbell: "button"
19894
+ };
19895
+ function legacyIcon(iconId) {
19896
+ return LEGACY_ICON[iconId] ?? "generic";
19897
+ }
19898
+ /**
19899
+ * Cap name → taxonomy kind id. Covers EVERY eventful sensor / control cap.
19900
+ * The anti-drift guard cross-checks this against the eventful caps declared
19901
+ * in `packages/types/src/capabilities/*.cap.ts`.
19902
+ */
19903
+ var CAP_TO_KIND = {
19904
+ contact: "contact",
19905
+ motion: "motion-sensor",
19906
+ smoke: "smoke",
19907
+ flood: "flood",
19908
+ gas: "gas",
19909
+ "carbon-monoxide": "carbon-monoxide",
19910
+ vibration: "vibration",
19911
+ tamper: "tamper",
19912
+ presence: "presence",
19913
+ "enum-sensor": "enum-sensor",
19914
+ "event-emitter": "device-event",
19915
+ "lock-control": "lock",
19916
+ switch: "switch",
19917
+ button: "button",
19918
+ doorbell: "doorbell"
19919
+ };
19920
+ function buildDescriptor(capName, kind) {
19921
+ const t = EVENT_TAXONOMY[kind];
19922
+ if (t === void 0) throw new Error(`EVENT_KIND_BY_CAP: cap '${capName}' maps to unknown taxonomy kind '${kind}'`);
19923
+ return {
19924
+ ...t,
19925
+ icon: legacyIcon(t.iconId)
19926
+ };
19927
+ }
19928
+ Object.freeze(Object.fromEntries(Object.entries(CAP_TO_KIND).map(([capName, kind]) => [capName, buildDescriptor(capName, kind)])));
19471
19929
  var CameraPipelineConfigSchema = object({
19472
19930
  engine: PipelineEngineChoiceSchema.optional(),
19473
19931
  steps: array(PipelineStepInputSchema).readonly(),
@@ -22694,13 +23152,29 @@ method(object({
22694
23152
  }), method(object({ deviceId: number() }), RecordingStatusSchema, {
22695
23153
  kind: "mutation",
22696
23154
  auth: "admin"
22697
- }), method(object({ deviceId: number() }), object({
23155
+ }), method(object({
23156
+ deviceId: number(),
23157
+ reason: OpsLogReasonSchema.optional()
23158
+ }), object({
22698
23159
  floorMs: number().nullable(),
22699
23160
  deletedBuckets: number().int(),
22700
23161
  reclaimedBytes: number().int()
22701
23162
  }), {
22702
23163
  kind: "mutation",
22703
23164
  auth: "admin"
23165
+ }), method(object({
23166
+ deviceId: number(),
23167
+ fromMs: number().optional(),
23168
+ toMs: number().optional()
23169
+ }), object({
23170
+ deletedBuckets: number().int(),
23171
+ reclaimedBytes: number().int()
23172
+ }), {
23173
+ kind: "mutation",
23174
+ auth: "admin"
23175
+ }), method(OpsLogQueryInputSchema, array(OpsLogEntrySchema).readonly(), {
23176
+ kind: "query",
23177
+ auth: "admin"
22704
23178
  });
22705
23179
  /**
22706
23180
  * `recordingExport` cap — render a footage time range into a single downloadable
@@ -25822,6 +26296,12 @@ Object.freeze({
25822
26296
  addonId: null,
25823
26297
  access: "delete"
25824
26298
  },
26299
+ "pipelineAnalytics.deleteDeviceEvents": {
26300
+ capName: "pipeline-analytics",
26301
+ capScope: "device",
26302
+ addonId: null,
26303
+ access: "delete"
26304
+ },
25825
26305
  "pipelineAnalytics.deleteTracks": {
25826
26306
  capName: "pipeline-analytics",
25827
26307
  capScope: "device",
@@ -25852,6 +26332,12 @@ Object.freeze({
25852
26332
  addonId: null,
25853
26333
  access: "view"
25854
26334
  },
26335
+ "pipelineAnalytics.getEventStoreFootprint": {
26336
+ capName: "pipeline-analytics",
26337
+ capScope: "device",
26338
+ addonId: null,
26339
+ access: "view"
26340
+ },
25855
26341
  "pipelineAnalytics.getKeyEvents": {
25856
26342
  capName: "pipeline-analytics",
25857
26343
  capScope: "device",
@@ -25894,6 +26380,12 @@ Object.freeze({
25894
26380
  addonId: null,
25895
26381
  access: "view"
25896
26382
  },
26383
+ "pipelineAnalytics.listOpsLog": {
26384
+ capName: "pipeline-analytics",
26385
+ capScope: "device",
26386
+ addonId: null,
26387
+ access: "view"
26388
+ },
25897
26389
  "pipelineAnalytics.listRecentTracks": {
25898
26390
  capName: "pipeline-analytics",
25899
26391
  capScope: "device",
@@ -25906,6 +26398,12 @@ Object.freeze({
25906
26398
  addonId: null,
25907
26399
  access: "view"
25908
26400
  },
26401
+ "pipelineAnalytics.pruneEvents": {
26402
+ capName: "pipeline-analytics",
26403
+ capScope: "device",
26404
+ addonId: null,
26405
+ access: "create"
26406
+ },
25909
26407
  "pipelineAnalytics.pruneEventsBefore": {
25910
26408
  capName: "pipeline-analytics",
25911
26409
  capScope: "device",
@@ -26668,6 +27166,12 @@ Object.freeze({
26668
27166
  addonId: null,
26669
27167
  access: "create"
26670
27168
  },
27169
+ "recording.deleteFootprint": {
27170
+ capName: "recording",
27171
+ capScope: "system",
27172
+ addonId: null,
27173
+ access: "delete"
27174
+ },
26671
27175
  "recording.getAvailability": {
26672
27176
  capName: "recording",
26673
27177
  capScope: "system",
@@ -26698,6 +27202,12 @@ Object.freeze({
26698
27202
  addonId: null,
26699
27203
  access: "view"
26700
27204
  },
27205
+ "recording.listOpsLog": {
27206
+ capName: "recording",
27207
+ capScope: "system",
27208
+ addonId: null,
27209
+ access: "view"
27210
+ },
26701
27211
  "recording.locateSegment": {
26702
27212
  capName: "recording",
26703
27213
  capScope: "system",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@camstack/addon-import-alexa",
3
- "version": "0.1.23",
3
+ "version": "0.1.25",
4
4
  "description": "Alexa device-import provider for CamStack — imports the smart-home devices in a user's Alexa account via the unofficial alexa-remote2 cookie/token client (the inverse of the Alexa exporter)",
5
5
  "keywords": [
6
6
  "camstack",