@camstack/addon-export-hap 1.2.38 → 1.2.39

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.
@@ -5886,6 +5886,13 @@ var BaseAddon = class {
5886
5886
  _readinessGeneration = typeof crypto !== "undefined" && crypto.randomUUID ? crypto.randomUUID() : Math.random().toString(36).slice(2, 14);
5887
5887
  /** Capability names this addon registered at init — used to emit matching `down` events on shutdown. */
5888
5888
  _registeredCapNames = [];
5889
+ /**
5890
+ * True only after `readAddonStore` actually answered. Constructor
5891
+ * defaults look like stored config when the store is down — a forked
5892
+ * addon that auto-starts from those defaults (cloudflare-tunnel quick
5893
+ * mode, 2026-08-25) is not "the operator chose this".
5894
+ */
5895
+ settingsStoreReady = false;
5889
5896
  /** Default config values. Provided via constructor. */
5890
5897
  defaults;
5891
5898
  constructor(defaults) {
@@ -6286,7 +6293,9 @@ var BaseAddon = class {
6286
6293
  ];
6287
6294
  let lastErr;
6288
6295
  for (let attempt = 0; attempt <= delaysMs.length; attempt++) try {
6289
- return await settings.readAddonStore() ?? {};
6296
+ const stored = await settings.readAddonStore() ?? {};
6297
+ this.settingsStoreReady = true;
6298
+ return stored;
6290
6299
  } catch (err) {
6291
6300
  lastErr = err;
6292
6301
  const msg = err instanceof Error ? err.message : String(err);
@@ -6294,6 +6303,7 @@ var BaseAddon = class {
6294
6303
  if (attempt === delaysMs.length) break;
6295
6304
  await new Promise((r) => setTimeout(r, delaysMs[attempt]));
6296
6305
  }
6306
+ this.settingsStoreReady = false;
6297
6307
  this._ctx?.logger?.warn?.("readAddonStore: settings-store unavailable after retries — using defaults", { meta: { error: lastErr instanceof Error ? lastErr.message : String(lastErr) } });
6298
6308
  return {};
6299
6309
  }
@@ -8832,6 +8842,12 @@ var ModelVariantGroupSchema = object({
8832
8842
  */
8833
8843
  resolution: number().int().positive().optional()
8834
8844
  });
8845
+ var ModelProviderIdSchema = _enum([
8846
+ "camstack",
8847
+ "frigate",
8848
+ "scrypted",
8849
+ "custom"
8850
+ ]);
8835
8851
  var ModelCatalogEntrySchema = object({
8836
8852
  id: string(),
8837
8853
  name: string(),
@@ -8929,6 +8945,12 @@ var ModelCatalogEntrySchema = object({
8929
8945
  */
8930
8946
  group: ModelVariantGroupSchema.optional(),
8931
8947
  /**
8948
+ * Catalog source for the pipeline stepper's provider-first picker. Absent on
8949
+ * built-in CamStack entries (treated as `camstack`) and on registry rows
8950
+ * persisted before this field existed (`inferModelProvider` fills those).
8951
+ */
8952
+ provider: ModelProviderIdSchema.optional(),
8953
+ /**
8932
8954
  * Per-MODEL class map override. Absent ⇒ the step's `StepDefinition.classMap`
8933
8955
  * applies (Frigate / COCO public catalog). Set on a custom model whose raw
8934
8956
  * labels already ARE the CamStack macros (Scrypted identity map).
@@ -12537,6 +12559,27 @@ var LinkedDeviceSchema = object({
12537
12559
  features: array(string()),
12538
12560
  producesTrackedEvents: boolean().optional()
12539
12561
  });
12562
+ /** One camera's resolved linked set, tagged with the camera it belongs to.
12563
+ * The batch answer needs the tag; the single-device answer already has it
12564
+ * from the input, which is why `getLinkedDevices` keeps the untagged shape. */
12565
+ var LinkedDevicesForDeviceSchema = object({
12566
+ deviceId: number(),
12567
+ mode: LinkedDevicesModeSchema,
12568
+ devices: array(LinkedDeviceSchema)
12569
+ });
12570
+ /** One device's binding map — the shape `getBindings`, `getBindingsBatch` and
12571
+ * `getAllBindings` all answer in. Declared once: three copies of the same
12572
+ * object literal is exactly how the three drift apart. */
12573
+ var DeviceBindingsForDeviceSchema = object({
12574
+ deviceId: number(),
12575
+ entries: array(object({
12576
+ capName: string(),
12577
+ kind: _enum(["native", "wrapped"]),
12578
+ providerAddonId: string(),
12579
+ providerNodeId: string(),
12580
+ nativeAddonId: string()
12581
+ }))
12582
+ });
12540
12583
  var SavedDeviceRowSchema = object({
12541
12584
  /** Numeric id reserved at allocateDeviceId time. */
12542
12585
  id: number(),
@@ -12762,11 +12805,25 @@ method(object({
12762
12805
  projection: _enum(["full", "slim"]).optional(),
12763
12806
  /** Return only camera devices. Filtering server-side instead of
12764
12807
  * shipping 293 rows to find 12. */
12765
- isCamera: boolean().optional()
12808
+ isCamera: boolean().optional(),
12809
+ /**
12810
+ * Return only these device ids. For the caller that already KNOWS the
12811
+ * handful it wants and needs a field the id-bearing answer does not
12812
+ * carry — the viewer's linked-devices panel joins `type` and `online`
12813
+ * onto ~8 linked ids and, unfiltered, dragged the fleet across to do
12814
+ * it: 433 KB slim / 958 KB full for 967 devices, on a query that
12815
+ * refetches on the reconcile interval, on a phone.
12816
+ *
12817
+ * Safe to send at a hub that predates it: Zod STRIPS unknown input
12818
+ * keys rather than rejecting them (verified against the live hub
12819
+ * 2026-08-25 — 967 rows came back), so an old hub answers exactly what
12820
+ * it answers today and the caller filters as it already does.
12821
+ */
12822
+ deviceIds: array(number()).optional()
12766
12823
  }), array(DeviceInfoSchema)), method(object({ deviceId: number() }), DeviceInfoSchema.nullable()), method(object({ parentDeviceId: number() }), array(DeviceInfoSchema)), method(object({ deviceId: number() }), object({
12767
12824
  mode: LinkedDevicesModeSchema,
12768
12825
  devices: array(LinkedDeviceSchema)
12769
- })), method(object({ deviceId: number() }), array(StreamSourceEntrySchema$1)), method(object({ deviceId: number() }), array(ConfigEntrySchema)), method(object({ deviceId: number() }), ConfigUISchemaOutput), method(object({
12826
+ })), method(object({ deviceIds: array(number()) }), array(LinkedDevicesForDeviceSchema)), method(object({ deviceId: number() }), array(StreamSourceEntrySchema$1)), method(object({ deviceId: number() }), array(ConfigEntrySchema)), method(object({ deviceId: number() }), ConfigUISchemaOutput), method(object({
12770
12827
  deviceId: number(),
12771
12828
  values: record(string(), unknown())
12772
12829
  }), object({ success: literal(true) }), {
@@ -12793,25 +12850,7 @@ method(object({
12793
12850
  }), method(object({ deviceId: number() }), array(StreamProbeResultSchema), {
12794
12851
  kind: "mutation",
12795
12852
  auth: "admin"
12796
- }), method(object({ deviceId: number() }), object({
12797
- deviceId: number(),
12798
- entries: array(object({
12799
- capName: string(),
12800
- kind: _enum(["native", "wrapped"]),
12801
- providerAddonId: string(),
12802
- providerNodeId: string(),
12803
- nativeAddonId: string()
12804
- }))
12805
- })), method(object({}), array(object({
12806
- deviceId: number(),
12807
- entries: array(object({
12808
- capName: string(),
12809
- kind: _enum(["native", "wrapped"]),
12810
- providerAddonId: string(),
12811
- providerNodeId: string(),
12812
- nativeAddonId: string()
12813
- }))
12814
- }))), method(object({
12853
+ }), method(object({ deviceId: number() }), DeviceBindingsForDeviceSchema), method(object({ deviceIds: array(number()) }), array(DeviceBindingsForDeviceSchema)), method(object({}), array(DeviceBindingsForDeviceSchema)), method(object({
12815
12854
  deviceId: number(),
12816
12855
  capName: string(),
12817
12856
  wrapperAddonId: string(),
@@ -15183,12 +15222,15 @@ var NcOccupancyConditionSchema = object({
15183
15222
  * there is no second switch that can disagree with the first and every rule
15184
15223
  * authored before the decision migrates for free (`audioModeOf`):
15185
15224
  *
15186
- * - **LABEL mode — `labels` present.** The rule fires on the FIRST frame the
15187
- * classifier labels with one of them. No window, no percentage:
15188
- * `hitPercent` and `samplingSeconds` are ignored, and the rule's own
15189
- * `throttle` cooldown is the only brake. The per-label confidence floor is
15190
- * the analyzer's (`classificationMinScore`, per device) a label only
15191
- * reaches this condition if the classifier was already confident enough.
15225
+ * - **LABEL mode — `labels` present.** The rule fires when `confirmHits`
15226
+ * labelled frames land inside `confirmWindowSec` (default 2 in 5 s).
15227
+ * `hitPercent` and `samplingSeconds` are still ignored a percentage of
15228
+ * frames is the wrong question for a classifier that labels 1–3 frames
15229
+ * per episode. The count window is the brake that drops a single-frame
15230
+ * false positive; the rule's own `throttle` cooldown is the other. The
15231
+ * per-label confidence floor is the analyzer's (`classificationMinScore`,
15232
+ * per device) — a label only reaches this condition if the classifier was
15233
+ * already confident enough. `confirmHits: 1` restores first-frame fire.
15192
15234
  * - **LEVEL mode — `dbThreshold` present, no labels.** The sampling window IS
15193
15235
  * the condition: at least `hitPercent`% of the samples over
15194
15236
  * `samplingSeconds` must be at or above `dbThreshold` dBFS (see
@@ -15215,14 +15257,22 @@ var NcOccupancyConditionSchema = object({
15215
15257
  * an operator who typed `dog` mean the same thing.
15216
15258
  */
15217
15259
  var NcAudioConditionSchema = object({
15218
- /** LABEL MODE: audio macro labels. Present ⇒ fires on the first labelled frame. */
15260
+ /** LABEL MODE: audio macro labels. Present ⇒ count-in-window confirm. */
15219
15261
  labels: array(string().min(1)).min(1).optional(),
15220
15262
  /** LEVEL MODE: floor in dBFS (negative-going, `0` = full scale). */
15221
15263
  dbThreshold: number().min(-96).max(0).optional(),
15222
15264
  /** LEVEL MODE ONLY: percentage of the window's samples that must be hits (1–100). */
15223
15265
  hitPercent: number().int().min(1).max(100).default(60),
15224
15266
  /** LEVEL MODE ONLY: length of the sampling window in seconds. */
15225
- samplingSeconds: number().int().min(1).max(300).default(10)
15267
+ samplingSeconds: number().int().min(1).max(300).default(10),
15268
+ /**
15269
+ * LABEL MODE: how many labelled frames must land inside
15270
+ * {@link NcAudioConditionSchema.shape.confirmWindowSec} before dispatch.
15271
+ * Absent ⇒ 2 (the matcher default). `1` is first-frame fire.
15272
+ */
15273
+ confirmHits: number().int().min(1).max(20).optional(),
15274
+ /** LABEL MODE: the window those frames must share, in seconds. Absent ⇒ 5. */
15275
+ confirmWindowSec: number().int().min(1).max(60).optional()
15226
15276
  });
15227
15277
  /**
15228
15278
  * Which zone-crossing DIRECTION a rule accepts (`ObjectEvent.zoneCrossing`).
@@ -17596,6 +17646,46 @@ var RecentTracksPageSchema = object({
17596
17646
  /** Cursor for the next page, or null when this page is the last. */
17597
17647
  nextCursor: string().nullable()
17598
17648
  });
17649
+ var LIST_GROUPS_DEFAULT_LIMIT = 40;
17650
+ var LIST_GROUPS_MAX_LIMIT = 100;
17651
+ var AnalyticsGroupRecordSchema = object({
17652
+ id: string(),
17653
+ deviceId: number().int(),
17654
+ openedAt: number().int(),
17655
+ closedAt: number().int(),
17656
+ timestamp: number().int(),
17657
+ memberCount: number().int(),
17658
+ memberTrackIds: array(string()).readonly(),
17659
+ className: string(),
17660
+ classes: array(string()).readonly(),
17661
+ /** Relative event-media path, or null when the group has no picture yet. */
17662
+ mediaUrl: string().nullable(),
17663
+ singleton: boolean()
17664
+ });
17665
+ var AnalyticsGroupMemberSchema = object({
17666
+ trackId: string(),
17667
+ deviceId: number().int(),
17668
+ className: string(),
17669
+ firstSeen: number().int(),
17670
+ lastSeen: number().int(),
17671
+ mediaUrl: string().nullable()
17672
+ });
17673
+ var AnalyticsGroupDetailSchema = AnalyticsGroupRecordSchema.extend({ members: array(AnalyticsGroupMemberSchema).readonly() });
17674
+ var ListGroupsQueryInput = object({
17675
+ /** Devices to merge. An empty array yields `{ groups: [], nextCursor: null }`. */
17676
+ deviceIds: array(number()),
17677
+ /** Window lower bound on `closedAt` (inclusive). */
17678
+ since: number().optional(),
17679
+ /** Window upper bound on `openedAt` (inclusive). */
17680
+ until: number().optional(),
17681
+ limit: number().int().min(1).max(LIST_GROUPS_MAX_LIMIT).default(LIST_GROUPS_DEFAULT_LIMIT),
17682
+ /** Opaque continuation cursor from a previous page's `nextCursor`. */
17683
+ cursor: string().optional()
17684
+ });
17685
+ var ListGroupsPageSchema = object({
17686
+ groups: array(AnalyticsGroupRecordSchema).readonly(),
17687
+ nextCursor: string().nullable()
17688
+ });
17599
17689
  var KeyEventQueryInput = object({
17600
17690
  deviceId: number(),
17601
17691
  /** Window lower bound (track firstSeen ≥ since). */
@@ -17671,7 +17761,9 @@ var TrackCascadeCountsSchema = object({
17671
17761
  /** Unassigned plate reads removed (best-effort; plate leg deferred to plate-intelligence). */
17672
17762
  plates: number().int(),
17673
17763
  /** Per-track CLIP search vectors removed (best-effort). */
17674
- embeddings: number().int()
17764
+ embeddings: number().int(),
17765
+ /** Group membership + group rows removed with their last member (best-effort). */
17766
+ groups: number().int()
17675
17767
  });
17676
17768
  /** Disk-wins reconcile: media rows whose blobs are gone, then empty tracks. */
17677
17769
  var DiskReconcileCountsSchema = object({
@@ -17817,7 +17909,10 @@ DeviceType.Camera, method(object({ deviceId: number() }), array(TrackSchema).rea
17817
17909
  * stationary registry). Default false: the timeline lists passages,
17818
17910
  * not parking records (operator decision, 2026-08-15). */
17819
17911
  includeStationary: boolean().optional()
17820
- }), array(TrackSchema).readonly()), method(RecentTracksQueryInput, RecentTracksPageSchema), method(object({ deviceId: number() }), _void(), {
17912
+ }), array(TrackSchema).readonly()), method(RecentTracksQueryInput, RecentTracksPageSchema), method(ListGroupsQueryInput, ListGroupsPageSchema), method(object({
17913
+ deviceId: number(),
17914
+ groupId: string().min(1)
17915
+ }), AnalyticsGroupDetailSchema.nullable()), method(object({ deviceId: number() }), _void(), {
17821
17916
  kind: "mutation",
17822
17917
  auth: "admin"
17823
17918
  }), method(DeviceEventQueryInput, array(MotionEventSchema).readonly()), method(ObjectEventQueryInput, array(ObjectEventSchema).readonly()), method(DeviceEventQueryInput, array(AudioEventSchema).readonly()), method(object({ deviceId: number() }), array(EventKindDescriptorSchema).readonly()), method(object({ deviceIds: array(number()).min(1).max(200) }), array(EventKindsForDeviceSchema).readonly()), method(object({
@@ -18127,7 +18222,8 @@ var PipelineModelOptionSchema = object({
18127
18222
  sizeMB: number()
18128
18223
  })),
18129
18224
  group: ModelVariantGroupSchema.optional(),
18130
- legacy: boolean().optional()
18225
+ legacy: boolean().optional(),
18226
+ provider: ModelProviderIdSchema.optional()
18131
18227
  });
18132
18228
  var ConfigFieldBridge = custom();
18133
18229
  var PipelineAddonSchemaSchema = object({
@@ -24759,7 +24855,12 @@ var PlateInfoSchema = object({
24759
24855
  plateBbox: BoundingBoxSchema.optional(),
24760
24856
  /** keyFrame parity: MediaStore key of the track's native-resolution key frame. */
24761
24857
  keyFrameMediaKey: string().optional(),
24762
- base64: string().optional()
24858
+ base64: string().optional(),
24859
+ /**
24860
+ * Same crop as a data-plane URL. `getPlateByTrack` returns this and
24861
+ * never inlines JPEG; `listPlates` still inlines for the admin-ui.
24862
+ */
24863
+ cropUrl: string().optional()
24763
24864
  });
24764
24865
  var MediaFileLiteSchema = object({
24765
24866
  key: string(),
@@ -28424,6 +28525,12 @@ Object.freeze({
28424
28525
  addonId: null,
28425
28526
  access: "view"
28426
28527
  },
28528
+ "deviceManager.getBindingsBatch": {
28529
+ capName: "device-manager",
28530
+ capScope: "system",
28531
+ addonId: null,
28532
+ access: "view"
28533
+ },
28427
28534
  "deviceManager.getChildren": {
28428
28535
  capName: "device-manager",
28429
28536
  capScope: "system",
@@ -28484,6 +28591,12 @@ Object.freeze({
28484
28591
  addonId: null,
28485
28592
  access: "view"
28486
28593
  },
28594
+ "deviceManager.getLinkedDevicesBatch": {
28595
+ capName: "device-manager",
28596
+ capScope: "system",
28597
+ addonId: null,
28598
+ access: "view"
28599
+ },
28487
28600
  "deviceManager.getRoleDisplayDefaults": {
28488
28601
  capName: "device-manager",
28489
28602
  capScope: "system",
@@ -30254,6 +30367,12 @@ Object.freeze({
30254
30367
  addonId: null,
30255
30368
  access: "view"
30256
30369
  },
30370
+ "pipelineAnalytics.getGroup": {
30371
+ capName: "pipeline-analytics",
30372
+ capScope: "device",
30373
+ addonId: null,
30374
+ access: "view"
30375
+ },
30257
30376
  "pipelineAnalytics.getKeyEvents": {
30258
30377
  capName: "pipeline-analytics",
30259
30378
  capScope: "device",
@@ -30338,6 +30457,12 @@ Object.freeze({
30338
30457
  addonId: null,
30339
30458
  access: "view"
30340
30459
  },
30460
+ "pipelineAnalytics.listGroups": {
30461
+ capName: "pipeline-analytics",
30462
+ capScope: "device",
30463
+ addonId: null,
30464
+ access: "view"
30465
+ },
30341
30466
  "pipelineAnalytics.listOpsLog": {
30342
30467
  capName: "pipeline-analytics",
30343
30468
  capScope: "device",
@@ -33119,6 +33244,11 @@ Object.freeze({
33119
33244
  form: "single",
33120
33245
  optional: false
33121
33246
  }],
33247
+ "deviceManager.getBindingsBatch": [{
33248
+ name: "deviceIds",
33249
+ form: "array",
33250
+ optional: false
33251
+ }],
33122
33252
  "deviceManager.getChildren": [{
33123
33253
  name: "parentDeviceId",
33124
33254
  form: "single",
@@ -33164,6 +33294,11 @@ Object.freeze({
33164
33294
  form: "single",
33165
33295
  optional: false
33166
33296
  }],
33297
+ "deviceManager.getLinkedDevicesBatch": [{
33298
+ name: "deviceIds",
33299
+ form: "array",
33300
+ optional: false
33301
+ }],
33167
33302
  "deviceManager.getSettingsSchema": [{
33168
33303
  name: "deviceId",
33169
33304
  form: "single",
@@ -33184,6 +33319,11 @@ Object.freeze({
33184
33319
  form: "single",
33185
33320
  optional: false
33186
33321
  }],
33322
+ "deviceManager.listAll": [{
33323
+ name: "deviceIds",
33324
+ form: "array",
33325
+ optional: true
33326
+ }],
33187
33327
  "deviceManager.loadConfig": [{
33188
33328
  name: "deviceId",
33189
33329
  form: "single",
@@ -33757,6 +33897,11 @@ Object.freeze({
33757
33897
  form: "single",
33758
33898
  optional: false
33759
33899
  }],
33900
+ "pipelineAnalytics.getGroup": [{
33901
+ name: "deviceId",
33902
+ form: "single",
33903
+ optional: false
33904
+ }],
33760
33905
  "pipelineAnalytics.getKeyEvents": [{
33761
33906
  name: "deviceId",
33762
33907
  form: "single",
@@ -33812,6 +33957,11 @@ Object.freeze({
33812
33957
  form: "array",
33813
33958
  optional: false
33814
33959
  }],
33960
+ "pipelineAnalytics.listGroups": [{
33961
+ name: "deviceIds",
33962
+ form: "array",
33963
+ optional: false
33964
+ }],
33815
33965
  "pipelineAnalytics.listOpsLog": [{
33816
33966
  name: "deviceId",
33817
33967
  form: "single",
@@ -5874,6 +5874,13 @@ var BaseAddon = class {
5874
5874
  _readinessGeneration = typeof crypto !== "undefined" && crypto.randomUUID ? crypto.randomUUID() : Math.random().toString(36).slice(2, 14);
5875
5875
  /** Capability names this addon registered at init — used to emit matching `down` events on shutdown. */
5876
5876
  _registeredCapNames = [];
5877
+ /**
5878
+ * True only after `readAddonStore` actually answered. Constructor
5879
+ * defaults look like stored config when the store is down — a forked
5880
+ * addon that auto-starts from those defaults (cloudflare-tunnel quick
5881
+ * mode, 2026-08-25) is not "the operator chose this".
5882
+ */
5883
+ settingsStoreReady = false;
5877
5884
  /** Default config values. Provided via constructor. */
5878
5885
  defaults;
5879
5886
  constructor(defaults) {
@@ -6274,7 +6281,9 @@ var BaseAddon = class {
6274
6281
  ];
6275
6282
  let lastErr;
6276
6283
  for (let attempt = 0; attempt <= delaysMs.length; attempt++) try {
6277
- return await settings.readAddonStore() ?? {};
6284
+ const stored = await settings.readAddonStore() ?? {};
6285
+ this.settingsStoreReady = true;
6286
+ return stored;
6278
6287
  } catch (err) {
6279
6288
  lastErr = err;
6280
6289
  const msg = err instanceof Error ? err.message : String(err);
@@ -6282,6 +6291,7 @@ var BaseAddon = class {
6282
6291
  if (attempt === delaysMs.length) break;
6283
6292
  await new Promise((r) => setTimeout(r, delaysMs[attempt]));
6284
6293
  }
6294
+ this.settingsStoreReady = false;
6285
6295
  this._ctx?.logger?.warn?.("readAddonStore: settings-store unavailable after retries — using defaults", { meta: { error: lastErr instanceof Error ? lastErr.message : String(lastErr) } });
6286
6296
  return {};
6287
6297
  }
@@ -8820,6 +8830,12 @@ var ModelVariantGroupSchema = object({
8820
8830
  */
8821
8831
  resolution: number().int().positive().optional()
8822
8832
  });
8833
+ var ModelProviderIdSchema = _enum([
8834
+ "camstack",
8835
+ "frigate",
8836
+ "scrypted",
8837
+ "custom"
8838
+ ]);
8823
8839
  var ModelCatalogEntrySchema = object({
8824
8840
  id: string(),
8825
8841
  name: string(),
@@ -8917,6 +8933,12 @@ var ModelCatalogEntrySchema = object({
8917
8933
  */
8918
8934
  group: ModelVariantGroupSchema.optional(),
8919
8935
  /**
8936
+ * Catalog source for the pipeline stepper's provider-first picker. Absent on
8937
+ * built-in CamStack entries (treated as `camstack`) and on registry rows
8938
+ * persisted before this field existed (`inferModelProvider` fills those).
8939
+ */
8940
+ provider: ModelProviderIdSchema.optional(),
8941
+ /**
8920
8942
  * Per-MODEL class map override. Absent ⇒ the step's `StepDefinition.classMap`
8921
8943
  * applies (Frigate / COCO public catalog). Set on a custom model whose raw
8922
8944
  * labels already ARE the CamStack macros (Scrypted identity map).
@@ -12525,6 +12547,27 @@ var LinkedDeviceSchema = object({
12525
12547
  features: array(string()),
12526
12548
  producesTrackedEvents: boolean().optional()
12527
12549
  });
12550
+ /** One camera's resolved linked set, tagged with the camera it belongs to.
12551
+ * The batch answer needs the tag; the single-device answer already has it
12552
+ * from the input, which is why `getLinkedDevices` keeps the untagged shape. */
12553
+ var LinkedDevicesForDeviceSchema = object({
12554
+ deviceId: number(),
12555
+ mode: LinkedDevicesModeSchema,
12556
+ devices: array(LinkedDeviceSchema)
12557
+ });
12558
+ /** One device's binding map — the shape `getBindings`, `getBindingsBatch` and
12559
+ * `getAllBindings` all answer in. Declared once: three copies of the same
12560
+ * object literal is exactly how the three drift apart. */
12561
+ var DeviceBindingsForDeviceSchema = object({
12562
+ deviceId: number(),
12563
+ entries: array(object({
12564
+ capName: string(),
12565
+ kind: _enum(["native", "wrapped"]),
12566
+ providerAddonId: string(),
12567
+ providerNodeId: string(),
12568
+ nativeAddonId: string()
12569
+ }))
12570
+ });
12528
12571
  var SavedDeviceRowSchema = object({
12529
12572
  /** Numeric id reserved at allocateDeviceId time. */
12530
12573
  id: number(),
@@ -12750,11 +12793,25 @@ method(object({
12750
12793
  projection: _enum(["full", "slim"]).optional(),
12751
12794
  /** Return only camera devices. Filtering server-side instead of
12752
12795
  * shipping 293 rows to find 12. */
12753
- isCamera: boolean().optional()
12796
+ isCamera: boolean().optional(),
12797
+ /**
12798
+ * Return only these device ids. For the caller that already KNOWS the
12799
+ * handful it wants and needs a field the id-bearing answer does not
12800
+ * carry — the viewer's linked-devices panel joins `type` and `online`
12801
+ * onto ~8 linked ids and, unfiltered, dragged the fleet across to do
12802
+ * it: 433 KB slim / 958 KB full for 967 devices, on a query that
12803
+ * refetches on the reconcile interval, on a phone.
12804
+ *
12805
+ * Safe to send at a hub that predates it: Zod STRIPS unknown input
12806
+ * keys rather than rejecting them (verified against the live hub
12807
+ * 2026-08-25 — 967 rows came back), so an old hub answers exactly what
12808
+ * it answers today and the caller filters as it already does.
12809
+ */
12810
+ deviceIds: array(number()).optional()
12754
12811
  }), array(DeviceInfoSchema)), method(object({ deviceId: number() }), DeviceInfoSchema.nullable()), method(object({ parentDeviceId: number() }), array(DeviceInfoSchema)), method(object({ deviceId: number() }), object({
12755
12812
  mode: LinkedDevicesModeSchema,
12756
12813
  devices: array(LinkedDeviceSchema)
12757
- })), method(object({ deviceId: number() }), array(StreamSourceEntrySchema$1)), method(object({ deviceId: number() }), array(ConfigEntrySchema)), method(object({ deviceId: number() }), ConfigUISchemaOutput), method(object({
12814
+ })), method(object({ deviceIds: array(number()) }), array(LinkedDevicesForDeviceSchema)), method(object({ deviceId: number() }), array(StreamSourceEntrySchema$1)), method(object({ deviceId: number() }), array(ConfigEntrySchema)), method(object({ deviceId: number() }), ConfigUISchemaOutput), method(object({
12758
12815
  deviceId: number(),
12759
12816
  values: record(string(), unknown())
12760
12817
  }), object({ success: literal(true) }), {
@@ -12781,25 +12838,7 @@ method(object({
12781
12838
  }), method(object({ deviceId: number() }), array(StreamProbeResultSchema), {
12782
12839
  kind: "mutation",
12783
12840
  auth: "admin"
12784
- }), method(object({ deviceId: number() }), object({
12785
- deviceId: number(),
12786
- entries: array(object({
12787
- capName: string(),
12788
- kind: _enum(["native", "wrapped"]),
12789
- providerAddonId: string(),
12790
- providerNodeId: string(),
12791
- nativeAddonId: string()
12792
- }))
12793
- })), method(object({}), array(object({
12794
- deviceId: number(),
12795
- entries: array(object({
12796
- capName: string(),
12797
- kind: _enum(["native", "wrapped"]),
12798
- providerAddonId: string(),
12799
- providerNodeId: string(),
12800
- nativeAddonId: string()
12801
- }))
12802
- }))), method(object({
12841
+ }), method(object({ deviceId: number() }), DeviceBindingsForDeviceSchema), method(object({ deviceIds: array(number()) }), array(DeviceBindingsForDeviceSchema)), method(object({}), array(DeviceBindingsForDeviceSchema)), method(object({
12803
12842
  deviceId: number(),
12804
12843
  capName: string(),
12805
12844
  wrapperAddonId: string(),
@@ -15171,12 +15210,15 @@ var NcOccupancyConditionSchema = object({
15171
15210
  * there is no second switch that can disagree with the first and every rule
15172
15211
  * authored before the decision migrates for free (`audioModeOf`):
15173
15212
  *
15174
- * - **LABEL mode — `labels` present.** The rule fires on the FIRST frame the
15175
- * classifier labels with one of them. No window, no percentage:
15176
- * `hitPercent` and `samplingSeconds` are ignored, and the rule's own
15177
- * `throttle` cooldown is the only brake. The per-label confidence floor is
15178
- * the analyzer's (`classificationMinScore`, per device) a label only
15179
- * reaches this condition if the classifier was already confident enough.
15213
+ * - **LABEL mode — `labels` present.** The rule fires when `confirmHits`
15214
+ * labelled frames land inside `confirmWindowSec` (default 2 in 5 s).
15215
+ * `hitPercent` and `samplingSeconds` are still ignored a percentage of
15216
+ * frames is the wrong question for a classifier that labels 1–3 frames
15217
+ * per episode. The count window is the brake that drops a single-frame
15218
+ * false positive; the rule's own `throttle` cooldown is the other. The
15219
+ * per-label confidence floor is the analyzer's (`classificationMinScore`,
15220
+ * per device) — a label only reaches this condition if the classifier was
15221
+ * already confident enough. `confirmHits: 1` restores first-frame fire.
15180
15222
  * - **LEVEL mode — `dbThreshold` present, no labels.** The sampling window IS
15181
15223
  * the condition: at least `hitPercent`% of the samples over
15182
15224
  * `samplingSeconds` must be at or above `dbThreshold` dBFS (see
@@ -15203,14 +15245,22 @@ var NcOccupancyConditionSchema = object({
15203
15245
  * an operator who typed `dog` mean the same thing.
15204
15246
  */
15205
15247
  var NcAudioConditionSchema = object({
15206
- /** LABEL MODE: audio macro labels. Present ⇒ fires on the first labelled frame. */
15248
+ /** LABEL MODE: audio macro labels. Present ⇒ count-in-window confirm. */
15207
15249
  labels: array(string().min(1)).min(1).optional(),
15208
15250
  /** LEVEL MODE: floor in dBFS (negative-going, `0` = full scale). */
15209
15251
  dbThreshold: number().min(-96).max(0).optional(),
15210
15252
  /** LEVEL MODE ONLY: percentage of the window's samples that must be hits (1–100). */
15211
15253
  hitPercent: number().int().min(1).max(100).default(60),
15212
15254
  /** LEVEL MODE ONLY: length of the sampling window in seconds. */
15213
- samplingSeconds: number().int().min(1).max(300).default(10)
15255
+ samplingSeconds: number().int().min(1).max(300).default(10),
15256
+ /**
15257
+ * LABEL MODE: how many labelled frames must land inside
15258
+ * {@link NcAudioConditionSchema.shape.confirmWindowSec} before dispatch.
15259
+ * Absent ⇒ 2 (the matcher default). `1` is first-frame fire.
15260
+ */
15261
+ confirmHits: number().int().min(1).max(20).optional(),
15262
+ /** LABEL MODE: the window those frames must share, in seconds. Absent ⇒ 5. */
15263
+ confirmWindowSec: number().int().min(1).max(60).optional()
15214
15264
  });
15215
15265
  /**
15216
15266
  * Which zone-crossing DIRECTION a rule accepts (`ObjectEvent.zoneCrossing`).
@@ -17584,6 +17634,46 @@ var RecentTracksPageSchema = object({
17584
17634
  /** Cursor for the next page, or null when this page is the last. */
17585
17635
  nextCursor: string().nullable()
17586
17636
  });
17637
+ var LIST_GROUPS_DEFAULT_LIMIT = 40;
17638
+ var LIST_GROUPS_MAX_LIMIT = 100;
17639
+ var AnalyticsGroupRecordSchema = object({
17640
+ id: string(),
17641
+ deviceId: number().int(),
17642
+ openedAt: number().int(),
17643
+ closedAt: number().int(),
17644
+ timestamp: number().int(),
17645
+ memberCount: number().int(),
17646
+ memberTrackIds: array(string()).readonly(),
17647
+ className: string(),
17648
+ classes: array(string()).readonly(),
17649
+ /** Relative event-media path, or null when the group has no picture yet. */
17650
+ mediaUrl: string().nullable(),
17651
+ singleton: boolean()
17652
+ });
17653
+ var AnalyticsGroupMemberSchema = object({
17654
+ trackId: string(),
17655
+ deviceId: number().int(),
17656
+ className: string(),
17657
+ firstSeen: number().int(),
17658
+ lastSeen: number().int(),
17659
+ mediaUrl: string().nullable()
17660
+ });
17661
+ var AnalyticsGroupDetailSchema = AnalyticsGroupRecordSchema.extend({ members: array(AnalyticsGroupMemberSchema).readonly() });
17662
+ var ListGroupsQueryInput = object({
17663
+ /** Devices to merge. An empty array yields `{ groups: [], nextCursor: null }`. */
17664
+ deviceIds: array(number()),
17665
+ /** Window lower bound on `closedAt` (inclusive). */
17666
+ since: number().optional(),
17667
+ /** Window upper bound on `openedAt` (inclusive). */
17668
+ until: number().optional(),
17669
+ limit: number().int().min(1).max(LIST_GROUPS_MAX_LIMIT).default(LIST_GROUPS_DEFAULT_LIMIT),
17670
+ /** Opaque continuation cursor from a previous page's `nextCursor`. */
17671
+ cursor: string().optional()
17672
+ });
17673
+ var ListGroupsPageSchema = object({
17674
+ groups: array(AnalyticsGroupRecordSchema).readonly(),
17675
+ nextCursor: string().nullable()
17676
+ });
17587
17677
  var KeyEventQueryInput = object({
17588
17678
  deviceId: number(),
17589
17679
  /** Window lower bound (track firstSeen ≥ since). */
@@ -17659,7 +17749,9 @@ var TrackCascadeCountsSchema = object({
17659
17749
  /** Unassigned plate reads removed (best-effort; plate leg deferred to plate-intelligence). */
17660
17750
  plates: number().int(),
17661
17751
  /** Per-track CLIP search vectors removed (best-effort). */
17662
- embeddings: number().int()
17752
+ embeddings: number().int(),
17753
+ /** Group membership + group rows removed with their last member (best-effort). */
17754
+ groups: number().int()
17663
17755
  });
17664
17756
  /** Disk-wins reconcile: media rows whose blobs are gone, then empty tracks. */
17665
17757
  var DiskReconcileCountsSchema = object({
@@ -17805,7 +17897,10 @@ DeviceType.Camera, method(object({ deviceId: number() }), array(TrackSchema).rea
17805
17897
  * stationary registry). Default false: the timeline lists passages,
17806
17898
  * not parking records (operator decision, 2026-08-15). */
17807
17899
  includeStationary: boolean().optional()
17808
- }), array(TrackSchema).readonly()), method(RecentTracksQueryInput, RecentTracksPageSchema), method(object({ deviceId: number() }), _void(), {
17900
+ }), array(TrackSchema).readonly()), method(RecentTracksQueryInput, RecentTracksPageSchema), method(ListGroupsQueryInput, ListGroupsPageSchema), method(object({
17901
+ deviceId: number(),
17902
+ groupId: string().min(1)
17903
+ }), AnalyticsGroupDetailSchema.nullable()), method(object({ deviceId: number() }), _void(), {
17809
17904
  kind: "mutation",
17810
17905
  auth: "admin"
17811
17906
  }), method(DeviceEventQueryInput, array(MotionEventSchema).readonly()), method(ObjectEventQueryInput, array(ObjectEventSchema).readonly()), method(DeviceEventQueryInput, array(AudioEventSchema).readonly()), method(object({ deviceId: number() }), array(EventKindDescriptorSchema).readonly()), method(object({ deviceIds: array(number()).min(1).max(200) }), array(EventKindsForDeviceSchema).readonly()), method(object({
@@ -18115,7 +18210,8 @@ var PipelineModelOptionSchema = object({
18115
18210
  sizeMB: number()
18116
18211
  })),
18117
18212
  group: ModelVariantGroupSchema.optional(),
18118
- legacy: boolean().optional()
18213
+ legacy: boolean().optional(),
18214
+ provider: ModelProviderIdSchema.optional()
18119
18215
  });
18120
18216
  var ConfigFieldBridge = custom();
18121
18217
  var PipelineAddonSchemaSchema = object({
@@ -24747,7 +24843,12 @@ var PlateInfoSchema = object({
24747
24843
  plateBbox: BoundingBoxSchema.optional(),
24748
24844
  /** keyFrame parity: MediaStore key of the track's native-resolution key frame. */
24749
24845
  keyFrameMediaKey: string().optional(),
24750
- base64: string().optional()
24846
+ base64: string().optional(),
24847
+ /**
24848
+ * Same crop as a data-plane URL. `getPlateByTrack` returns this and
24849
+ * never inlines JPEG; `listPlates` still inlines for the admin-ui.
24850
+ */
24851
+ cropUrl: string().optional()
24751
24852
  });
24752
24853
  var MediaFileLiteSchema = object({
24753
24854
  key: string(),
@@ -28412,6 +28513,12 @@ Object.freeze({
28412
28513
  addonId: null,
28413
28514
  access: "view"
28414
28515
  },
28516
+ "deviceManager.getBindingsBatch": {
28517
+ capName: "device-manager",
28518
+ capScope: "system",
28519
+ addonId: null,
28520
+ access: "view"
28521
+ },
28415
28522
  "deviceManager.getChildren": {
28416
28523
  capName: "device-manager",
28417
28524
  capScope: "system",
@@ -28472,6 +28579,12 @@ Object.freeze({
28472
28579
  addonId: null,
28473
28580
  access: "view"
28474
28581
  },
28582
+ "deviceManager.getLinkedDevicesBatch": {
28583
+ capName: "device-manager",
28584
+ capScope: "system",
28585
+ addonId: null,
28586
+ access: "view"
28587
+ },
28475
28588
  "deviceManager.getRoleDisplayDefaults": {
28476
28589
  capName: "device-manager",
28477
28590
  capScope: "system",
@@ -30242,6 +30355,12 @@ Object.freeze({
30242
30355
  addonId: null,
30243
30356
  access: "view"
30244
30357
  },
30358
+ "pipelineAnalytics.getGroup": {
30359
+ capName: "pipeline-analytics",
30360
+ capScope: "device",
30361
+ addonId: null,
30362
+ access: "view"
30363
+ },
30245
30364
  "pipelineAnalytics.getKeyEvents": {
30246
30365
  capName: "pipeline-analytics",
30247
30366
  capScope: "device",
@@ -30326,6 +30445,12 @@ Object.freeze({
30326
30445
  addonId: null,
30327
30446
  access: "view"
30328
30447
  },
30448
+ "pipelineAnalytics.listGroups": {
30449
+ capName: "pipeline-analytics",
30450
+ capScope: "device",
30451
+ addonId: null,
30452
+ access: "view"
30453
+ },
30329
30454
  "pipelineAnalytics.listOpsLog": {
30330
30455
  capName: "pipeline-analytics",
30331
30456
  capScope: "device",
@@ -33107,6 +33232,11 @@ Object.freeze({
33107
33232
  form: "single",
33108
33233
  optional: false
33109
33234
  }],
33235
+ "deviceManager.getBindingsBatch": [{
33236
+ name: "deviceIds",
33237
+ form: "array",
33238
+ optional: false
33239
+ }],
33110
33240
  "deviceManager.getChildren": [{
33111
33241
  name: "parentDeviceId",
33112
33242
  form: "single",
@@ -33152,6 +33282,11 @@ Object.freeze({
33152
33282
  form: "single",
33153
33283
  optional: false
33154
33284
  }],
33285
+ "deviceManager.getLinkedDevicesBatch": [{
33286
+ name: "deviceIds",
33287
+ form: "array",
33288
+ optional: false
33289
+ }],
33155
33290
  "deviceManager.getSettingsSchema": [{
33156
33291
  name: "deviceId",
33157
33292
  form: "single",
@@ -33172,6 +33307,11 @@ Object.freeze({
33172
33307
  form: "single",
33173
33308
  optional: false
33174
33309
  }],
33310
+ "deviceManager.listAll": [{
33311
+ name: "deviceIds",
33312
+ form: "array",
33313
+ optional: true
33314
+ }],
33175
33315
  "deviceManager.loadConfig": [{
33176
33316
  name: "deviceId",
33177
33317
  form: "single",
@@ -33745,6 +33885,11 @@ Object.freeze({
33745
33885
  form: "single",
33746
33886
  optional: false
33747
33887
  }],
33888
+ "pipelineAnalytics.getGroup": [{
33889
+ name: "deviceId",
33890
+ form: "single",
33891
+ optional: false
33892
+ }],
33748
33893
  "pipelineAnalytics.getKeyEvents": [{
33749
33894
  name: "deviceId",
33750
33895
  form: "single",
@@ -33800,6 +33945,11 @@ Object.freeze({
33800
33945
  form: "array",
33801
33946
  optional: false
33802
33947
  }],
33948
+ "pipelineAnalytics.listGroups": [{
33949
+ name: "deviceIds",
33950
+ form: "array",
33951
+ optional: false
33952
+ }],
33803
33953
  "pipelineAnalytics.listOpsLog": [{
33804
33954
  name: "deviceId",
33805
33955
  form: "single",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@camstack/addon-export-hap",
3
- "version": "1.2.38",
3
+ "version": "1.2.39",
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",