@camstack/addon-provider-wyze 0.2.29 → 0.2.31

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.
Files changed (3) hide show
  1. package/dist/addon.js +258 -17
  2. package/dist/addon.mjs +258 -17
  3. package/package.json +1 -1
package/dist/addon.js CHANGED
@@ -31,7 +31,7 @@ let net = require("net");
31
31
  net = __toESM(net, 1);
32
32
  let node_child_process = require("node:child_process");
33
33
  let node_tls = require("node:tls");
34
- //#region ../types/dist/event-category-XfKNtfCc.mjs
34
+ //#region ../types/dist/event-category-CIa_iT6b.mjs
35
35
  var EventCategory = /* @__PURE__ */ function(EventCategory) {
36
36
  EventCategory["SystemBoot"] = "system.boot";
37
37
  EventCategory["SystemAddonsReady"] = "system.addons-ready";
@@ -47,6 +47,15 @@ var EventCategory = /* @__PURE__ */ function(EventCategory) {
47
47
  */
48
48
  EventCategory["SystemRestartCompleted"] = "system.restart-completed";
49
49
  /**
50
+ * The hub reissued its own TLS certificate at boot (`ensureTlsCert`).
51
+ * Emitted only when the material on disk actually changed, so an
52
+ * operator who trusted the old certificate by hand is told rather than
53
+ * discovering it as a browser error. Payload `TlsCertChangedPayload`.
54
+ *
55
+ * Rule: docs/decisions/adr-0227-*.md
56
+ */
57
+ EventCategory["SystemTlsCertChanged"] = "system.tls-cert-changed";
58
+ /**
50
59
  * A newer addon or server-root package version was found by the
51
60
  * authoritative registry check. Emitted once when any observed
52
61
  * `latestVersion` changes (or a package/node first appears behind);
@@ -8043,6 +8052,15 @@ var LabelDefinitionSchema = object({
8043
8052
  description: string().optional(),
8044
8053
  icon: string().optional()
8045
8054
  });
8055
+ var ClassMapDefinitionSchema = object({
8056
+ mapping: record(string(), _enum([
8057
+ "person",
8058
+ "vehicle",
8059
+ "animal",
8060
+ "package"
8061
+ ])),
8062
+ preserveOriginal: boolean()
8063
+ });
8046
8064
  var MODEL_FORMATS = [
8047
8065
  "onnx",
8048
8066
  "coreml",
@@ -8221,7 +8239,13 @@ var ModelCatalogEntrySchema = object({
8221
8239
  * `id` stays the source of truth for resolution/download/persistence; grouping
8222
8240
  * is a presentation overlay resolved back to an `id`.
8223
8241
  */
8224
- group: ModelVariantGroupSchema.optional()
8242
+ group: ModelVariantGroupSchema.optional(),
8243
+ /**
8244
+ * Per-MODEL class map override. Absent ⇒ the step's `StepDefinition.classMap`
8245
+ * applies (Frigate / COCO public catalog). Set on a custom model whose raw
8246
+ * labels already ARE the CamStack macros (Scrypted identity map).
8247
+ */
8248
+ classMap: ClassMapDefinitionSchema.optional()
8225
8249
  });
8226
8250
  var ConvertTargetSchema = discriminatedUnion("format", [object({
8227
8251
  format: literal("openvino"),
@@ -8250,7 +8274,8 @@ var ModelConvertMetadataSchema = object({
8250
8274
  "ocr",
8251
8275
  "segmentation"
8252
8276
  ]),
8253
- faceAlignment: boolean().optional()
8277
+ faceAlignment: boolean().optional(),
8278
+ classMap: ClassMapDefinitionSchema.optional()
8254
8279
  });
8255
8280
  var ConvertResultSchema = object({
8256
8281
  entry: ModelCatalogEntrySchema,
@@ -17560,6 +17585,33 @@ var NativeCropRefSchema = object({
17560
17585
  h: number()
17561
17586
  })
17562
17587
  });
17588
+ object({
17589
+ crop: object({
17590
+ left: number(),
17591
+ top: number(),
17592
+ width: number().positive(),
17593
+ height: number().positive()
17594
+ }).optional(),
17595
+ content: object({
17596
+ width: number().int().positive(),
17597
+ height: number().int().positive()
17598
+ }),
17599
+ fit: _enum(["stretch", "contain"]),
17600
+ format: _enum([
17601
+ "rgb",
17602
+ "gray",
17603
+ "jpeg"
17604
+ ])
17605
+ });
17606
+ var FrameRefSchema = object({
17607
+ registryId: string().min(1),
17608
+ id: string().min(1),
17609
+ width: number().int().positive(),
17610
+ height: number().int().positive(),
17611
+ format: _enum(["rgb", "gray"]),
17612
+ timestamp: number(),
17613
+ capturedAt: number().optional()
17614
+ });
17563
17615
  var ModelFormatSchema$1 = _enum([
17564
17616
  "onnx",
17565
17617
  "coreml",
@@ -17804,6 +17856,12 @@ method(_void(), array(PipelineEngineChoiceSchema)), method(_void(), PipelineEngi
17804
17856
  steps: array(PipelineStepInputSchema).min(1),
17805
17857
  frame: FrameInputSchema.optional(),
17806
17858
  /**
17859
+ * Process-local lazy frame. Valid only when caller and provider resolve
17860
+ * in the same execution-group process; split/cross-node callers use
17861
+ * `frame`/`image` inline compatibility instead.
17862
+ */
17863
+ frameRef: FrameRefSchema.optional(),
17864
+ /**
17807
17865
  * CB5 shm passthrough — a `FrameHandle` naming the same ring slot
17808
17866
  * the decoded pixels live in. One more member of the one-of
17809
17867
  * frame/frameHandle/image/imageBase64/referenceImage group.
@@ -18099,7 +18157,10 @@ var NativeCropResultSchema = object({
18099
18157
  * Which source served this crop, so a quality-sensitive consumer (the native
18100
18158
  * `keyFrame`) can reject a degraded fallback:
18101
18159
  * - `native` — cut from the decode worker's retained NATIVE surface (the
18102
- * quality path).
18160
+ * quality path). A subject-tile serve is also native-resolution and stays
18161
+ * `native` here: the public enum cannot name `tile` without a breaking cap
18162
+ * change. Runner telemetry distinguishes lease vs tile via `source` on the
18163
+ * internal crop result (`nativeHits` vs `tileHits`).
18103
18164
  * - `ram-fullframe` — the native surface MISSED but the request was full-frame,
18104
18165
  * so the ≤640 RAM `RetainedFrameStore` served it (honest lower-res; legit for
18105
18166
  * the blank-frame guard / 640-snapshot resolve, NOT for the clean keyFrame).
@@ -18590,12 +18651,41 @@ var RunnerLocalLoadSchema = object({
18590
18651
  * legacy `OrchestratorMetricsSchema` shape so existing dashboards keep
18591
18652
  * working unchanged when they switch to reading from the runner cap.
18592
18653
  */
18654
+ var FrameLazyCountersSchema = object({
18655
+ framesDecoded: number(),
18656
+ framesAdmitted: number(),
18657
+ framesDroppedPixelFree: number(),
18658
+ viewsMaterialized: number(),
18659
+ viewsSkipped: number(),
18660
+ workerToRunnerBytes: number(),
18661
+ runnerToPoolRawBytes: number(),
18662
+ runnerToPoolJpegBytes: number(),
18663
+ onDemandFullFrameRequests: number(),
18664
+ onDemandCropRequests: number(),
18665
+ nativeHits: number(),
18666
+ nativeMisses: number(),
18667
+ tileHits: number(),
18668
+ tileMisses: number(),
18669
+ fallbackHits: number(),
18670
+ fallbackMisses: number(),
18671
+ retainedWritesAvoided: number(),
18672
+ residentRefs: number(),
18673
+ residentBytes: number(),
18674
+ releases: number(),
18675
+ evictions: number(),
18676
+ staleMisses: number()
18677
+ });
18678
+ var FrameLazyMetricsSchema = object({
18679
+ node: FrameLazyCountersSchema,
18680
+ cameras: array(FrameLazyCountersSchema.extend({ deviceId: number() }))
18681
+ });
18593
18682
  var RunnerLocalMetricsSchema = object({
18594
18683
  nodeId: string(),
18595
18684
  activeCameras: number(),
18596
18685
  throttledCameras: number(),
18597
18686
  avgInferenceTimeMs: number(),
18598
- queueDepth: number()
18687
+ queueDepth: number(),
18688
+ frameLazy: FrameLazyMetricsSchema.optional()
18599
18689
  });
18600
18690
  method(RunnerCameraConfigSchema, object({ success: literal(true) }), { kind: "mutation" }), method(object({ deviceId: number() }), object({ success: literal(true) }), { kind: "mutation" }), method(ReportMotionInputSchema, object({ success: literal(true) }), { kind: "mutation" }), method(_void(), RunnerLocalLoadSchema), method(_void(), RunnerLocalMetricsSchema), method(object({ deviceId: number() }), CameraMetricsSchema.nullable()), method(_void(), array(CameraMetricsWithDeviceIdSchema).readonly()), method(_void(), array(number()).readonly()), method(object({
18601
18691
  handle: FrameHandleSchema,
@@ -19999,6 +20089,9 @@ method(_void(), ProviderInfoSchema), method(object({ config: record(string(), un
19999
20089
  location: StorageLocationSchema,
20000
20090
  relativePath: string()
20001
20091
  }), _void(), { kind: "mutation" }), method(object({ location: StorageLocationSchema }), number().nullable()), method(BeginUploadInputSchema, BeginUploadResultSchema, { kind: "mutation" }), method(WriteChunkInputSchema, _void(), { kind: "mutation" }), method(FinalizeUploadInputSchema, _void(), { kind: "mutation" }), method(AbortUploadInputSchema, _void(), { kind: "mutation" }), method(BeginDownloadInputSchema, BeginDownloadResultSchema, { kind: "mutation" }), method(ReadChunkInputSchema, _instanceof(Uint8Array)), method(EndDownloadInputSchema, _void(), { kind: "mutation" });
20092
+ /** Profile-exported FormBuilder schema. Shape is ConfigUISchema at the UI. */
20093
+ var ProfileSettingsSchemaBridge = unknown().nullable();
20094
+ var ProfileSettingsBagSchema = record(string(), unknown());
20002
20095
  /**
20003
20096
  * A live terminal session hosted by the provider addon. Output and input do
20004
20097
  * NOT flow through the capability — they use the addon data plane
@@ -20028,7 +20121,14 @@ var TerminalSessionInfoSchema = object({
20028
20121
  var TerminalProfileInfoSchema = object({
20029
20122
  profileId: string(),
20030
20123
  label: string(),
20031
- description: string().optional()
20124
+ description: string().optional(),
20125
+ /** Spawn defaults the instance form copies on create. */
20126
+ executable: string().optional(),
20127
+ args: array(string()).readonly().optional(),
20128
+ cwd: string().optional(),
20129
+ environment: array(string()).readonly().optional(),
20130
+ /** ConfigUISchema for instance knobs, or null when the profile has none. */
20131
+ settingsSchema: ProfileSettingsSchemaBridge.optional()
20032
20132
  });
20033
20133
  /**
20034
20134
  * A durable operator-created Terminal instance. Profiles are templates; only
@@ -20041,7 +20141,12 @@ var TerminalInstanceInfoSchema = object({
20041
20141
  profileId: string(),
20042
20142
  profileLabel: string(),
20043
20143
  name: string(),
20044
- enabled: boolean()
20144
+ enabled: boolean(),
20145
+ executable: string(),
20146
+ args: array(string()).readonly(),
20147
+ cwd: string(),
20148
+ environment: array(string()).readonly(),
20149
+ profileSettings: ProfileSettingsBagSchema
20045
20150
  });
20046
20151
  var TerminalLegacyCameraSchema = object({
20047
20152
  stableId: string(),
@@ -20071,7 +20176,23 @@ var TerminalOutputBatchSchema = object({
20071
20176
  method(_void(), array(TerminalProfileInfoSchema).readonly(), { auth: "admin" }), method(_void(), array(TerminalInstanceInfoSchema).readonly(), { auth: "admin" }), method(object({
20072
20177
  targetNodeId: string().min(1),
20073
20178
  profileId: string().min(1),
20074
- name: string().trim().min(1).max(160).optional()
20179
+ name: string().trim().min(1).max(160).optional(),
20180
+ executable: string().max(1024).optional(),
20181
+ args: array(string().max(2048)).max(64).optional(),
20182
+ cwd: string().max(1024).optional(),
20183
+ environment: array(string().max(4096)).max(64).optional(),
20184
+ profileSettings: ProfileSettingsBagSchema.optional()
20185
+ }), TerminalInstanceInfoSchema, {
20186
+ kind: "mutation",
20187
+ auth: "admin"
20188
+ }), method(object({
20189
+ instanceId: string().min(1),
20190
+ name: string().trim().min(1).max(160).optional(),
20191
+ executable: string().max(1024).optional(),
20192
+ args: array(string().max(2048)).max(64).optional(),
20193
+ cwd: string().max(1024).optional(),
20194
+ environment: array(string().max(4096)).max(64).optional(),
20195
+ profileSettings: ProfileSettingsBagSchema.optional()
20075
20196
  }), TerminalInstanceInfoSchema, {
20076
20197
  kind: "mutation",
20077
20198
  auth: "admin"
@@ -20093,7 +20214,11 @@ method(_void(), array(TerminalProfileInfoSchema).readonly(), { auth: "admin" }),
20093
20214
  }), method(_void(), array(TerminalSessionInfoSchema).readonly(), { auth: "admin" }), method(object({
20094
20215
  profileId: string(),
20095
20216
  cols: number().int().positive(),
20096
- rows: number().int().positive()
20217
+ rows: number().int().positive(),
20218
+ executable: string().max(1024).optional(),
20219
+ args: array(string().max(2048)).max(64).optional(),
20220
+ cwd: string().max(1024).optional(),
20221
+ environment: array(string().max(4096)).max(64).optional()
20097
20222
  }), TerminalSessionInfoSchema, {
20098
20223
  kind: "mutation",
20099
20224
  auth: "admin"
@@ -23976,10 +24101,10 @@ var lawnMowerControlCapability = {
23976
24101
  *
23977
24102
  * • The SDK (mobile / web client) consumes `getConnectionEndpoints()`
23978
24103
  * to receive an ordered list of candidate base URLs it should race
23979
- * on connect — LAN IPv4 first (lowest latency when on same network),
23980
- * then public hostname (if a tunnel is up), then IPv6. The SDK can
23981
- * race them with short timeouts and stick with the winner for the
23982
- * session.
24104
+ * on connect — LAN IPv4 and stable LAN IPv6 first (lowest latency
24105
+ * when on the same network), then public hostname (if a tunnel is
24106
+ * up). The SDK can race them with short timeouts and stick with the
24107
+ * winner for the session.
23983
24108
  *
23984
24109
  * Why hub-only: agents are not directly addressable by the operator's
23985
24110
  * clients — they reverse-connect to the hub. Exposing their interfaces
@@ -24134,6 +24259,17 @@ var NotificationEndpointSchema = object({
24134
24259
  /** What the ranking currently resolves to (null when nothing is reachable). */
24135
24260
  resolved: string().nullable()
24136
24261
  });
24262
+ /**
24263
+ * The URLs the SDK / viewer should race for API access. `baseUrls` empty =
24264
+ * AUTO (every LAN IPv4 + the public tunnel). `resolved` is what that choice
24265
+ * currently expands to, so the UI can show the effective set either way.
24266
+ */
24267
+ var ViewerEndpointsSchema = object({
24268
+ /** The operator's explicit race set, or empty for AUTO. */
24269
+ baseUrls: array(string()).readonly(),
24270
+ /** What the ranking currently resolves to (may be empty if nothing is up). */
24271
+ resolved: array(string()).readonly()
24272
+ });
24137
24273
  var AllowedAddressesSchema = object({
24138
24274
  /**
24139
24275
  * Allowlist of interface addresses operators have explicitly opted
@@ -24142,6 +24278,20 @@ var AllowedAddressesSchema = object({
24142
24278
  * Network Addresses admin page and persisted by the addon.
24143
24279
  */
24144
24280
  addresses: array(string()).readonly() });
24281
+ var TlsStatusSchema = object({
24282
+ mode: _enum([
24283
+ "generated",
24284
+ "uploaded",
24285
+ "disabled"
24286
+ ]),
24287
+ leafFingerprintSha256: string().nullable(),
24288
+ caFingerprintSha256: string().nullable(),
24289
+ validTo: string().nullable(),
24290
+ sans: array(string()),
24291
+ caCertPem: string().nullable(),
24292
+ reissueError: string().nullable(),
24293
+ restartRequired: boolean()
24294
+ });
24145
24295
  method(_void(), ListResultSchema), method(_void(), PreferredSchema), method(object({
24146
24296
  /**
24147
24297
  * LEGACY HINT — do not send from new code. Kept optional so clients
@@ -24151,17 +24301,31 @@ method(_void(), ListResultSchema), method(_void(), PreferredSchema), method(obje
24151
24301
  */
24152
24302
  port: number().int().min(1).max(65535).optional(),
24153
24303
  /** Include `http(s)://127.0.0.1:<port>` as the lowest-priority
24154
- * candidate. Default `true`. */
24304
+ * candidate. Default `false` — loopback is not a client route. */
24155
24305
  includeLoopback: boolean().optional(),
24156
- /** Skip IPv6 entries. Some legacy clients can't parse them.
24157
- * Default `false`. */
24306
+ /** Skip IPv6 entries. Default `false` the palette includes stable
24307
+ * LAN IPv6 (ICE/WebRTC uses dual-stack regardless). Pass `true` to
24308
+ * hide them. The viewer HTTP/WS race is `getViewerEndpoints`. */
24158
24309
  ipv4Only: boolean().optional(),
24159
24310
  /** Scheme to emit for LAN/loopback URLs. Default `'http'`.
24160
24311
  * Pass `'https'` when the caller is itself loaded over HTTPS
24161
24312
  * to avoid mixed-content blocks in the browser. The public
24162
24313
  * tunnel always emits `https://` regardless. */
24163
24314
  scheme: _enum(["http", "https"]).optional()
24164
- }), GetConnectionEndpointsResultSchema), method(_void(), NotificationEndpointSchema), method(object({ baseUrl: string().nullable() }), NotificationEndpointSchema, { kind: "mutation" }), method(_void(), AllowedAddressesSchema), method(AllowedAddressesSchema, object({ success: literal(true) }), { kind: "mutation" }), method(_void(), AllowedAddressesSchema, { kind: "mutation" });
24315
+ }), GetConnectionEndpointsResultSchema), method(_void(), NotificationEndpointSchema), method(object({ baseUrl: string().nullable() }), NotificationEndpointSchema, { kind: "mutation" }), method(_void(), ViewerEndpointsSchema), method(object({ baseUrls: array(string()).readonly() }), ViewerEndpointsSchema, { kind: "mutation" }), method(_void(), AllowedAddressesSchema), method(AllowedAddressesSchema, object({ success: literal(true) }), { kind: "mutation" }), method(_void(), AllowedAddressesSchema, { kind: "mutation" }), method(_void(), TlsStatusSchema), method(object({ reason: string().optional() }), TlsStatusSchema, {
24316
+ kind: "mutation",
24317
+ auth: "admin"
24318
+ }), method(object({
24319
+ certPem: string().min(1),
24320
+ keyPem: string().min(1),
24321
+ caPem: string().optional()
24322
+ }), TlsStatusSchema, {
24323
+ kind: "mutation",
24324
+ auth: "admin"
24325
+ }), method(_void(), object({ pem: string() })), method(_void(), TlsStatusSchema, {
24326
+ kind: "mutation",
24327
+ auth: "admin"
24328
+ });
24165
24329
  var LockControlStatusSchema = object({
24166
24330
  /** Lifecycle state of the lock. `jammed` means the motor reported
24167
24331
  * failure to reach the target — operator intervention required. */
@@ -32188,6 +32352,12 @@ Object.freeze({
32188
32352
  addonId: null,
32189
32353
  access: "create"
32190
32354
  },
32355
+ "localNetwork.downloadCa": {
32356
+ capName: "local-network",
32357
+ capScope: "system",
32358
+ addonId: null,
32359
+ access: "view"
32360
+ },
32191
32361
  "localNetwork.getAllowedAddresses": {
32192
32362
  capName: "local-network",
32193
32363
  capScope: "system",
@@ -32212,18 +32382,42 @@ Object.freeze({
32212
32382
  addonId: null,
32213
32383
  access: "view"
32214
32384
  },
32385
+ "localNetwork.getTlsStatus": {
32386
+ capName: "local-network",
32387
+ capScope: "system",
32388
+ addonId: null,
32389
+ access: "view"
32390
+ },
32391
+ "localNetwork.getViewerEndpoints": {
32392
+ capName: "local-network",
32393
+ capScope: "system",
32394
+ addonId: null,
32395
+ access: "view"
32396
+ },
32215
32397
  "localNetwork.list": {
32216
32398
  capName: "local-network",
32217
32399
  capScope: "system",
32218
32400
  addonId: null,
32219
32401
  access: "view"
32220
32402
  },
32403
+ "localNetwork.regenerateCertificate": {
32404
+ capName: "local-network",
32405
+ capScope: "system",
32406
+ addonId: null,
32407
+ access: "create"
32408
+ },
32221
32409
  "localNetwork.resetAllowlistToBestMatch": {
32222
32410
  capName: "local-network",
32223
32411
  capScope: "system",
32224
32412
  addonId: null,
32225
32413
  access: "delete"
32226
32414
  },
32415
+ "localNetwork.revertToGeneratedCertificate": {
32416
+ capName: "local-network",
32417
+ capScope: "system",
32418
+ addonId: null,
32419
+ access: "create"
32420
+ },
32227
32421
  "localNetwork.setAllowedAddresses": {
32228
32422
  capName: "local-network",
32229
32423
  capScope: "system",
@@ -32236,6 +32430,18 @@ Object.freeze({
32236
32430
  addonId: null,
32237
32431
  access: "create"
32238
32432
  },
32433
+ "localNetwork.setViewerEndpoints": {
32434
+ capName: "local-network",
32435
+ capScope: "system",
32436
+ addonId: null,
32437
+ access: "create"
32438
+ },
32439
+ "localNetwork.uploadCertificate": {
32440
+ capName: "local-network",
32441
+ capScope: "system",
32442
+ addonId: null,
32443
+ access: "create"
32444
+ },
32239
32445
  "lockControl.lock": {
32240
32446
  capName: "lock-control",
32241
32447
  capScope: "device",
@@ -35116,6 +35322,12 @@ Object.freeze({
35116
35322
  addonId: null,
35117
35323
  access: "create"
35118
35324
  },
35325
+ "terminalSession.updateInstance": {
35326
+ capName: "terminal-session",
35327
+ capScope: "system",
35328
+ addonId: null,
35329
+ access: "create"
35330
+ },
35119
35331
  "terminalSession.writeInput": {
35120
35332
  capName: "terminal-session",
35121
35333
  capScope: "system",
@@ -37603,6 +37815,35 @@ Object.freeze(Object.fromEntries([{
37603
37815
  }]
37604
37816
  }].map((s) => [s.stepId, s.defaultModelId])));
37605
37817
  string().min(1);
37818
+ var CLUSTER_STEP_SETTING_FIELDS = [{
37819
+ stepId: "face-embedding",
37820
+ key: "minLandmarkFaceSize",
37821
+ label: "Min face size for recognition (detection px)",
37822
+ description: "Refuse to embed a face smaller than this IN THE DETECTION FRAME. Applies to every node — the enrolled gallery is one index, and two admission floors would fill it from two policies. 0 disables the gate.",
37823
+ type: "slider",
37824
+ min: 0,
37825
+ max: 64,
37826
+ step: 2,
37827
+ default: 24
37828
+ }];
37829
+ function clusterStepSettingKey(stepId, fieldKey) {
37830
+ return `clusterStepSetting:${stepId}:${fieldKey}`;
37831
+ }
37832
+ var ClusterSettingNumberSchema = number().finite();
37833
+ function readClusterStepSettings(config) {
37834
+ const out = {};
37835
+ for (const field of CLUSTER_STEP_SETTING_FIELDS) {
37836
+ const parsed = ClusterSettingNumberSchema.safeParse(config[clusterStepSettingKey(field.stepId, field.key)]);
37837
+ const value = parsed.success ? parsed.data : field.default;
37838
+ const existing = out[field.stepId] ?? {};
37839
+ out[field.stepId] = {
37840
+ ...existing,
37841
+ [field.key]: value
37842
+ };
37843
+ }
37844
+ return out;
37845
+ }
37846
+ readClusterStepSettings({});
37606
37847
  object({
37607
37848
  /**
37608
37849
  * Fraction of the box's own size added on EACH side before cutting.
package/dist/addon.mjs CHANGED
@@ -10,7 +10,7 @@ import { getCACertificates, setDefaultCACertificates } from "node:tls";
10
10
  //#region \0rolldown/runtime.js
11
11
  var __require$1 = /* @__PURE__ */ createRequire(import.meta.url);
12
12
  //#endregion
13
- //#region ../types/dist/event-category-XfKNtfCc.mjs
13
+ //#region ../types/dist/event-category-CIa_iT6b.mjs
14
14
  var EventCategory = /* @__PURE__ */ function(EventCategory) {
15
15
  EventCategory["SystemBoot"] = "system.boot";
16
16
  EventCategory["SystemAddonsReady"] = "system.addons-ready";
@@ -26,6 +26,15 @@ var EventCategory = /* @__PURE__ */ function(EventCategory) {
26
26
  */
27
27
  EventCategory["SystemRestartCompleted"] = "system.restart-completed";
28
28
  /**
29
+ * The hub reissued its own TLS certificate at boot (`ensureTlsCert`).
30
+ * Emitted only when the material on disk actually changed, so an
31
+ * operator who trusted the old certificate by hand is told rather than
32
+ * discovering it as a browser error. Payload `TlsCertChangedPayload`.
33
+ *
34
+ * Rule: docs/decisions/adr-0227-*.md
35
+ */
36
+ EventCategory["SystemTlsCertChanged"] = "system.tls-cert-changed";
37
+ /**
29
38
  * A newer addon or server-root package version was found by the
30
39
  * authoritative registry check. Emitted once when any observed
31
40
  * `latestVersion` changes (or a package/node first appears behind);
@@ -8022,6 +8031,15 @@ var LabelDefinitionSchema = object({
8022
8031
  description: string().optional(),
8023
8032
  icon: string().optional()
8024
8033
  });
8034
+ var ClassMapDefinitionSchema = object({
8035
+ mapping: record(string(), _enum([
8036
+ "person",
8037
+ "vehicle",
8038
+ "animal",
8039
+ "package"
8040
+ ])),
8041
+ preserveOriginal: boolean()
8042
+ });
8025
8043
  var MODEL_FORMATS = [
8026
8044
  "onnx",
8027
8045
  "coreml",
@@ -8200,7 +8218,13 @@ var ModelCatalogEntrySchema = object({
8200
8218
  * `id` stays the source of truth for resolution/download/persistence; grouping
8201
8219
  * is a presentation overlay resolved back to an `id`.
8202
8220
  */
8203
- group: ModelVariantGroupSchema.optional()
8221
+ group: ModelVariantGroupSchema.optional(),
8222
+ /**
8223
+ * Per-MODEL class map override. Absent ⇒ the step's `StepDefinition.classMap`
8224
+ * applies (Frigate / COCO public catalog). Set on a custom model whose raw
8225
+ * labels already ARE the CamStack macros (Scrypted identity map).
8226
+ */
8227
+ classMap: ClassMapDefinitionSchema.optional()
8204
8228
  });
8205
8229
  var ConvertTargetSchema = discriminatedUnion("format", [object({
8206
8230
  format: literal("openvino"),
@@ -8229,7 +8253,8 @@ var ModelConvertMetadataSchema = object({
8229
8253
  "ocr",
8230
8254
  "segmentation"
8231
8255
  ]),
8232
- faceAlignment: boolean().optional()
8256
+ faceAlignment: boolean().optional(),
8257
+ classMap: ClassMapDefinitionSchema.optional()
8233
8258
  });
8234
8259
  var ConvertResultSchema = object({
8235
8260
  entry: ModelCatalogEntrySchema,
@@ -17539,6 +17564,33 @@ var NativeCropRefSchema = object({
17539
17564
  h: number()
17540
17565
  })
17541
17566
  });
17567
+ object({
17568
+ crop: object({
17569
+ left: number(),
17570
+ top: number(),
17571
+ width: number().positive(),
17572
+ height: number().positive()
17573
+ }).optional(),
17574
+ content: object({
17575
+ width: number().int().positive(),
17576
+ height: number().int().positive()
17577
+ }),
17578
+ fit: _enum(["stretch", "contain"]),
17579
+ format: _enum([
17580
+ "rgb",
17581
+ "gray",
17582
+ "jpeg"
17583
+ ])
17584
+ });
17585
+ var FrameRefSchema = object({
17586
+ registryId: string().min(1),
17587
+ id: string().min(1),
17588
+ width: number().int().positive(),
17589
+ height: number().int().positive(),
17590
+ format: _enum(["rgb", "gray"]),
17591
+ timestamp: number(),
17592
+ capturedAt: number().optional()
17593
+ });
17542
17594
  var ModelFormatSchema$1 = _enum([
17543
17595
  "onnx",
17544
17596
  "coreml",
@@ -17783,6 +17835,12 @@ method(_void(), array(PipelineEngineChoiceSchema)), method(_void(), PipelineEngi
17783
17835
  steps: array(PipelineStepInputSchema).min(1),
17784
17836
  frame: FrameInputSchema.optional(),
17785
17837
  /**
17838
+ * Process-local lazy frame. Valid only when caller and provider resolve
17839
+ * in the same execution-group process; split/cross-node callers use
17840
+ * `frame`/`image` inline compatibility instead.
17841
+ */
17842
+ frameRef: FrameRefSchema.optional(),
17843
+ /**
17786
17844
  * CB5 shm passthrough — a `FrameHandle` naming the same ring slot
17787
17845
  * the decoded pixels live in. One more member of the one-of
17788
17846
  * frame/frameHandle/image/imageBase64/referenceImage group.
@@ -18078,7 +18136,10 @@ var NativeCropResultSchema = object({
18078
18136
  * Which source served this crop, so a quality-sensitive consumer (the native
18079
18137
  * `keyFrame`) can reject a degraded fallback:
18080
18138
  * - `native` — cut from the decode worker's retained NATIVE surface (the
18081
- * quality path).
18139
+ * quality path). A subject-tile serve is also native-resolution and stays
18140
+ * `native` here: the public enum cannot name `tile` without a breaking cap
18141
+ * change. Runner telemetry distinguishes lease vs tile via `source` on the
18142
+ * internal crop result (`nativeHits` vs `tileHits`).
18082
18143
  * - `ram-fullframe` — the native surface MISSED but the request was full-frame,
18083
18144
  * so the ≤640 RAM `RetainedFrameStore` served it (honest lower-res; legit for
18084
18145
  * the blank-frame guard / 640-snapshot resolve, NOT for the clean keyFrame).
@@ -18569,12 +18630,41 @@ var RunnerLocalLoadSchema = object({
18569
18630
  * legacy `OrchestratorMetricsSchema` shape so existing dashboards keep
18570
18631
  * working unchanged when they switch to reading from the runner cap.
18571
18632
  */
18633
+ var FrameLazyCountersSchema = object({
18634
+ framesDecoded: number(),
18635
+ framesAdmitted: number(),
18636
+ framesDroppedPixelFree: number(),
18637
+ viewsMaterialized: number(),
18638
+ viewsSkipped: number(),
18639
+ workerToRunnerBytes: number(),
18640
+ runnerToPoolRawBytes: number(),
18641
+ runnerToPoolJpegBytes: number(),
18642
+ onDemandFullFrameRequests: number(),
18643
+ onDemandCropRequests: number(),
18644
+ nativeHits: number(),
18645
+ nativeMisses: number(),
18646
+ tileHits: number(),
18647
+ tileMisses: number(),
18648
+ fallbackHits: number(),
18649
+ fallbackMisses: number(),
18650
+ retainedWritesAvoided: number(),
18651
+ residentRefs: number(),
18652
+ residentBytes: number(),
18653
+ releases: number(),
18654
+ evictions: number(),
18655
+ staleMisses: number()
18656
+ });
18657
+ var FrameLazyMetricsSchema = object({
18658
+ node: FrameLazyCountersSchema,
18659
+ cameras: array(FrameLazyCountersSchema.extend({ deviceId: number() }))
18660
+ });
18572
18661
  var RunnerLocalMetricsSchema = object({
18573
18662
  nodeId: string(),
18574
18663
  activeCameras: number(),
18575
18664
  throttledCameras: number(),
18576
18665
  avgInferenceTimeMs: number(),
18577
- queueDepth: number()
18666
+ queueDepth: number(),
18667
+ frameLazy: FrameLazyMetricsSchema.optional()
18578
18668
  });
18579
18669
  method(RunnerCameraConfigSchema, object({ success: literal(true) }), { kind: "mutation" }), method(object({ deviceId: number() }), object({ success: literal(true) }), { kind: "mutation" }), method(ReportMotionInputSchema, object({ success: literal(true) }), { kind: "mutation" }), method(_void(), RunnerLocalLoadSchema), method(_void(), RunnerLocalMetricsSchema), method(object({ deviceId: number() }), CameraMetricsSchema.nullable()), method(_void(), array(CameraMetricsWithDeviceIdSchema).readonly()), method(_void(), array(number()).readonly()), method(object({
18580
18670
  handle: FrameHandleSchema,
@@ -19978,6 +20068,9 @@ method(_void(), ProviderInfoSchema), method(object({ config: record(string(), un
19978
20068
  location: StorageLocationSchema,
19979
20069
  relativePath: string()
19980
20070
  }), _void(), { kind: "mutation" }), method(object({ location: StorageLocationSchema }), number().nullable()), method(BeginUploadInputSchema, BeginUploadResultSchema, { kind: "mutation" }), method(WriteChunkInputSchema, _void(), { kind: "mutation" }), method(FinalizeUploadInputSchema, _void(), { kind: "mutation" }), method(AbortUploadInputSchema, _void(), { kind: "mutation" }), method(BeginDownloadInputSchema, BeginDownloadResultSchema, { kind: "mutation" }), method(ReadChunkInputSchema, _instanceof(Uint8Array)), method(EndDownloadInputSchema, _void(), { kind: "mutation" });
20071
+ /** Profile-exported FormBuilder schema. Shape is ConfigUISchema at the UI. */
20072
+ var ProfileSettingsSchemaBridge = unknown().nullable();
20073
+ var ProfileSettingsBagSchema = record(string(), unknown());
19981
20074
  /**
19982
20075
  * A live terminal session hosted by the provider addon. Output and input do
19983
20076
  * NOT flow through the capability — they use the addon data plane
@@ -20007,7 +20100,14 @@ var TerminalSessionInfoSchema = object({
20007
20100
  var TerminalProfileInfoSchema = object({
20008
20101
  profileId: string(),
20009
20102
  label: string(),
20010
- description: string().optional()
20103
+ description: string().optional(),
20104
+ /** Spawn defaults the instance form copies on create. */
20105
+ executable: string().optional(),
20106
+ args: array(string()).readonly().optional(),
20107
+ cwd: string().optional(),
20108
+ environment: array(string()).readonly().optional(),
20109
+ /** ConfigUISchema for instance knobs, or null when the profile has none. */
20110
+ settingsSchema: ProfileSettingsSchemaBridge.optional()
20011
20111
  });
20012
20112
  /**
20013
20113
  * A durable operator-created Terminal instance. Profiles are templates; only
@@ -20020,7 +20120,12 @@ var TerminalInstanceInfoSchema = object({
20020
20120
  profileId: string(),
20021
20121
  profileLabel: string(),
20022
20122
  name: string(),
20023
- enabled: boolean()
20123
+ enabled: boolean(),
20124
+ executable: string(),
20125
+ args: array(string()).readonly(),
20126
+ cwd: string(),
20127
+ environment: array(string()).readonly(),
20128
+ profileSettings: ProfileSettingsBagSchema
20024
20129
  });
20025
20130
  var TerminalLegacyCameraSchema = object({
20026
20131
  stableId: string(),
@@ -20050,7 +20155,23 @@ var TerminalOutputBatchSchema = object({
20050
20155
  method(_void(), array(TerminalProfileInfoSchema).readonly(), { auth: "admin" }), method(_void(), array(TerminalInstanceInfoSchema).readonly(), { auth: "admin" }), method(object({
20051
20156
  targetNodeId: string().min(1),
20052
20157
  profileId: string().min(1),
20053
- name: string().trim().min(1).max(160).optional()
20158
+ name: string().trim().min(1).max(160).optional(),
20159
+ executable: string().max(1024).optional(),
20160
+ args: array(string().max(2048)).max(64).optional(),
20161
+ cwd: string().max(1024).optional(),
20162
+ environment: array(string().max(4096)).max(64).optional(),
20163
+ profileSettings: ProfileSettingsBagSchema.optional()
20164
+ }), TerminalInstanceInfoSchema, {
20165
+ kind: "mutation",
20166
+ auth: "admin"
20167
+ }), method(object({
20168
+ instanceId: string().min(1),
20169
+ name: string().trim().min(1).max(160).optional(),
20170
+ executable: string().max(1024).optional(),
20171
+ args: array(string().max(2048)).max(64).optional(),
20172
+ cwd: string().max(1024).optional(),
20173
+ environment: array(string().max(4096)).max(64).optional(),
20174
+ profileSettings: ProfileSettingsBagSchema.optional()
20054
20175
  }), TerminalInstanceInfoSchema, {
20055
20176
  kind: "mutation",
20056
20177
  auth: "admin"
@@ -20072,7 +20193,11 @@ method(_void(), array(TerminalProfileInfoSchema).readonly(), { auth: "admin" }),
20072
20193
  }), method(_void(), array(TerminalSessionInfoSchema).readonly(), { auth: "admin" }), method(object({
20073
20194
  profileId: string(),
20074
20195
  cols: number().int().positive(),
20075
- rows: number().int().positive()
20196
+ rows: number().int().positive(),
20197
+ executable: string().max(1024).optional(),
20198
+ args: array(string().max(2048)).max(64).optional(),
20199
+ cwd: string().max(1024).optional(),
20200
+ environment: array(string().max(4096)).max(64).optional()
20076
20201
  }), TerminalSessionInfoSchema, {
20077
20202
  kind: "mutation",
20078
20203
  auth: "admin"
@@ -23955,10 +24080,10 @@ var lawnMowerControlCapability = {
23955
24080
  *
23956
24081
  * • The SDK (mobile / web client) consumes `getConnectionEndpoints()`
23957
24082
  * to receive an ordered list of candidate base URLs it should race
23958
- * on connect — LAN IPv4 first (lowest latency when on same network),
23959
- * then public hostname (if a tunnel is up), then IPv6. The SDK can
23960
- * race them with short timeouts and stick with the winner for the
23961
- * session.
24083
+ * on connect — LAN IPv4 and stable LAN IPv6 first (lowest latency
24084
+ * when on the same network), then public hostname (if a tunnel is
24085
+ * up). The SDK can race them with short timeouts and stick with the
24086
+ * winner for the session.
23962
24087
  *
23963
24088
  * Why hub-only: agents are not directly addressable by the operator's
23964
24089
  * clients — they reverse-connect to the hub. Exposing their interfaces
@@ -24113,6 +24238,17 @@ var NotificationEndpointSchema = object({
24113
24238
  /** What the ranking currently resolves to (null when nothing is reachable). */
24114
24239
  resolved: string().nullable()
24115
24240
  });
24241
+ /**
24242
+ * The URLs the SDK / viewer should race for API access. `baseUrls` empty =
24243
+ * AUTO (every LAN IPv4 + the public tunnel). `resolved` is what that choice
24244
+ * currently expands to, so the UI can show the effective set either way.
24245
+ */
24246
+ var ViewerEndpointsSchema = object({
24247
+ /** The operator's explicit race set, or empty for AUTO. */
24248
+ baseUrls: array(string()).readonly(),
24249
+ /** What the ranking currently resolves to (may be empty if nothing is up). */
24250
+ resolved: array(string()).readonly()
24251
+ });
24116
24252
  var AllowedAddressesSchema = object({
24117
24253
  /**
24118
24254
  * Allowlist of interface addresses operators have explicitly opted
@@ -24121,6 +24257,20 @@ var AllowedAddressesSchema = object({
24121
24257
  * Network Addresses admin page and persisted by the addon.
24122
24258
  */
24123
24259
  addresses: array(string()).readonly() });
24260
+ var TlsStatusSchema = object({
24261
+ mode: _enum([
24262
+ "generated",
24263
+ "uploaded",
24264
+ "disabled"
24265
+ ]),
24266
+ leafFingerprintSha256: string().nullable(),
24267
+ caFingerprintSha256: string().nullable(),
24268
+ validTo: string().nullable(),
24269
+ sans: array(string()),
24270
+ caCertPem: string().nullable(),
24271
+ reissueError: string().nullable(),
24272
+ restartRequired: boolean()
24273
+ });
24124
24274
  method(_void(), ListResultSchema), method(_void(), PreferredSchema), method(object({
24125
24275
  /**
24126
24276
  * LEGACY HINT — do not send from new code. Kept optional so clients
@@ -24130,17 +24280,31 @@ method(_void(), ListResultSchema), method(_void(), PreferredSchema), method(obje
24130
24280
  */
24131
24281
  port: number().int().min(1).max(65535).optional(),
24132
24282
  /** Include `http(s)://127.0.0.1:<port>` as the lowest-priority
24133
- * candidate. Default `true`. */
24283
+ * candidate. Default `false` — loopback is not a client route. */
24134
24284
  includeLoopback: boolean().optional(),
24135
- /** Skip IPv6 entries. Some legacy clients can't parse them.
24136
- * Default `false`. */
24285
+ /** Skip IPv6 entries. Default `false` the palette includes stable
24286
+ * LAN IPv6 (ICE/WebRTC uses dual-stack regardless). Pass `true` to
24287
+ * hide them. The viewer HTTP/WS race is `getViewerEndpoints`. */
24137
24288
  ipv4Only: boolean().optional(),
24138
24289
  /** Scheme to emit for LAN/loopback URLs. Default `'http'`.
24139
24290
  * Pass `'https'` when the caller is itself loaded over HTTPS
24140
24291
  * to avoid mixed-content blocks in the browser. The public
24141
24292
  * tunnel always emits `https://` regardless. */
24142
24293
  scheme: _enum(["http", "https"]).optional()
24143
- }), GetConnectionEndpointsResultSchema), method(_void(), NotificationEndpointSchema), method(object({ baseUrl: string().nullable() }), NotificationEndpointSchema, { kind: "mutation" }), method(_void(), AllowedAddressesSchema), method(AllowedAddressesSchema, object({ success: literal(true) }), { kind: "mutation" }), method(_void(), AllowedAddressesSchema, { kind: "mutation" });
24294
+ }), GetConnectionEndpointsResultSchema), method(_void(), NotificationEndpointSchema), method(object({ baseUrl: string().nullable() }), NotificationEndpointSchema, { kind: "mutation" }), method(_void(), ViewerEndpointsSchema), method(object({ baseUrls: array(string()).readonly() }), ViewerEndpointsSchema, { kind: "mutation" }), method(_void(), AllowedAddressesSchema), method(AllowedAddressesSchema, object({ success: literal(true) }), { kind: "mutation" }), method(_void(), AllowedAddressesSchema, { kind: "mutation" }), method(_void(), TlsStatusSchema), method(object({ reason: string().optional() }), TlsStatusSchema, {
24295
+ kind: "mutation",
24296
+ auth: "admin"
24297
+ }), method(object({
24298
+ certPem: string().min(1),
24299
+ keyPem: string().min(1),
24300
+ caPem: string().optional()
24301
+ }), TlsStatusSchema, {
24302
+ kind: "mutation",
24303
+ auth: "admin"
24304
+ }), method(_void(), object({ pem: string() })), method(_void(), TlsStatusSchema, {
24305
+ kind: "mutation",
24306
+ auth: "admin"
24307
+ });
24144
24308
  var LockControlStatusSchema = object({
24145
24309
  /** Lifecycle state of the lock. `jammed` means the motor reported
24146
24310
  * failure to reach the target — operator intervention required. */
@@ -32167,6 +32331,12 @@ Object.freeze({
32167
32331
  addonId: null,
32168
32332
  access: "create"
32169
32333
  },
32334
+ "localNetwork.downloadCa": {
32335
+ capName: "local-network",
32336
+ capScope: "system",
32337
+ addonId: null,
32338
+ access: "view"
32339
+ },
32170
32340
  "localNetwork.getAllowedAddresses": {
32171
32341
  capName: "local-network",
32172
32342
  capScope: "system",
@@ -32191,18 +32361,42 @@ Object.freeze({
32191
32361
  addonId: null,
32192
32362
  access: "view"
32193
32363
  },
32364
+ "localNetwork.getTlsStatus": {
32365
+ capName: "local-network",
32366
+ capScope: "system",
32367
+ addonId: null,
32368
+ access: "view"
32369
+ },
32370
+ "localNetwork.getViewerEndpoints": {
32371
+ capName: "local-network",
32372
+ capScope: "system",
32373
+ addonId: null,
32374
+ access: "view"
32375
+ },
32194
32376
  "localNetwork.list": {
32195
32377
  capName: "local-network",
32196
32378
  capScope: "system",
32197
32379
  addonId: null,
32198
32380
  access: "view"
32199
32381
  },
32382
+ "localNetwork.regenerateCertificate": {
32383
+ capName: "local-network",
32384
+ capScope: "system",
32385
+ addonId: null,
32386
+ access: "create"
32387
+ },
32200
32388
  "localNetwork.resetAllowlistToBestMatch": {
32201
32389
  capName: "local-network",
32202
32390
  capScope: "system",
32203
32391
  addonId: null,
32204
32392
  access: "delete"
32205
32393
  },
32394
+ "localNetwork.revertToGeneratedCertificate": {
32395
+ capName: "local-network",
32396
+ capScope: "system",
32397
+ addonId: null,
32398
+ access: "create"
32399
+ },
32206
32400
  "localNetwork.setAllowedAddresses": {
32207
32401
  capName: "local-network",
32208
32402
  capScope: "system",
@@ -32215,6 +32409,18 @@ Object.freeze({
32215
32409
  addonId: null,
32216
32410
  access: "create"
32217
32411
  },
32412
+ "localNetwork.setViewerEndpoints": {
32413
+ capName: "local-network",
32414
+ capScope: "system",
32415
+ addonId: null,
32416
+ access: "create"
32417
+ },
32418
+ "localNetwork.uploadCertificate": {
32419
+ capName: "local-network",
32420
+ capScope: "system",
32421
+ addonId: null,
32422
+ access: "create"
32423
+ },
32218
32424
  "lockControl.lock": {
32219
32425
  capName: "lock-control",
32220
32426
  capScope: "device",
@@ -35095,6 +35301,12 @@ Object.freeze({
35095
35301
  addonId: null,
35096
35302
  access: "create"
35097
35303
  },
35304
+ "terminalSession.updateInstance": {
35305
+ capName: "terminal-session",
35306
+ capScope: "system",
35307
+ addonId: null,
35308
+ access: "create"
35309
+ },
35098
35310
  "terminalSession.writeInput": {
35099
35311
  capName: "terminal-session",
35100
35312
  capScope: "system",
@@ -37582,6 +37794,35 @@ Object.freeze(Object.fromEntries([{
37582
37794
  }]
37583
37795
  }].map((s) => [s.stepId, s.defaultModelId])));
37584
37796
  string().min(1);
37797
+ var CLUSTER_STEP_SETTING_FIELDS = [{
37798
+ stepId: "face-embedding",
37799
+ key: "minLandmarkFaceSize",
37800
+ label: "Min face size for recognition (detection px)",
37801
+ description: "Refuse to embed a face smaller than this IN THE DETECTION FRAME. Applies to every node — the enrolled gallery is one index, and two admission floors would fill it from two policies. 0 disables the gate.",
37802
+ type: "slider",
37803
+ min: 0,
37804
+ max: 64,
37805
+ step: 2,
37806
+ default: 24
37807
+ }];
37808
+ function clusterStepSettingKey(stepId, fieldKey) {
37809
+ return `clusterStepSetting:${stepId}:${fieldKey}`;
37810
+ }
37811
+ var ClusterSettingNumberSchema = number().finite();
37812
+ function readClusterStepSettings(config) {
37813
+ const out = {};
37814
+ for (const field of CLUSTER_STEP_SETTING_FIELDS) {
37815
+ const parsed = ClusterSettingNumberSchema.safeParse(config[clusterStepSettingKey(field.stepId, field.key)]);
37816
+ const value = parsed.success ? parsed.data : field.default;
37817
+ const existing = out[field.stepId] ?? {};
37818
+ out[field.stepId] = {
37819
+ ...existing,
37820
+ [field.key]: value
37821
+ };
37822
+ }
37823
+ return out;
37824
+ }
37825
+ readClusterStepSettings({});
37585
37826
  object({
37586
37827
  /**
37587
37828
  * Fraction of the box's own size added on EACH side before cutting.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@camstack/addon-provider-wyze",
3
- "version": "0.2.29",
3
+ "version": "0.2.31",
4
4
  "description": "Wyze camera device-provider addon for CamStack — wraps the @apocaliss92/wyze-bridge-js P2P/DTLS client, feeding the stream-broker via the pull-rfc4571 lazy-publish path (a structural twin of addon-provider-reolink)",
5
5
  "keywords": [
6
6
  "camstack",