@camstack/addon-provider-dreo 0.2.25 → 0.2.27

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
@@ -35,7 +35,7 @@ var __toCommonJS = (mod) => __hasOwnProp.call(mod, "module.exports") ? mod["modu
35
35
  //#endregion
36
36
  let crypto$1 = require("crypto");
37
37
  let events = require("events");
38
- //#region ../types/dist/event-category-XfKNtfCc.mjs
38
+ //#region ../types/dist/event-category-CIa_iT6b.mjs
39
39
  var EventCategory = /* @__PURE__ */ function(EventCategory) {
40
40
  EventCategory["SystemBoot"] = "system.boot";
41
41
  EventCategory["SystemAddonsReady"] = "system.addons-ready";
@@ -51,6 +51,15 @@ var EventCategory = /* @__PURE__ */ function(EventCategory) {
51
51
  */
52
52
  EventCategory["SystemRestartCompleted"] = "system.restart-completed";
53
53
  /**
54
+ * The hub reissued its own TLS certificate at boot (`ensureTlsCert`).
55
+ * Emitted only when the material on disk actually changed, so an
56
+ * operator who trusted the old certificate by hand is told rather than
57
+ * discovering it as a browser error. Payload `TlsCertChangedPayload`.
58
+ *
59
+ * Rule: docs/decisions/adr-0227-*.md
60
+ */
61
+ EventCategory["SystemTlsCertChanged"] = "system.tls-cert-changed";
62
+ /**
54
63
  * A newer addon or server-root package version was found by the
55
64
  * authoritative registry check. Emitted once when any observed
56
65
  * `latestVersion` changes (or a package/node first appears behind);
@@ -8047,6 +8056,15 @@ var LabelDefinitionSchema = object({
8047
8056
  description: string().optional(),
8048
8057
  icon: string().optional()
8049
8058
  });
8059
+ var ClassMapDefinitionSchema = object({
8060
+ mapping: record(string(), _enum([
8061
+ "person",
8062
+ "vehicle",
8063
+ "animal",
8064
+ "package"
8065
+ ])),
8066
+ preserveOriginal: boolean()
8067
+ });
8050
8068
  var MODEL_FORMATS = [
8051
8069
  "onnx",
8052
8070
  "coreml",
@@ -8225,7 +8243,13 @@ var ModelCatalogEntrySchema = object({
8225
8243
  * `id` stays the source of truth for resolution/download/persistence; grouping
8226
8244
  * is a presentation overlay resolved back to an `id`.
8227
8245
  */
8228
- group: ModelVariantGroupSchema.optional()
8246
+ group: ModelVariantGroupSchema.optional(),
8247
+ /**
8248
+ * Per-MODEL class map override. Absent ⇒ the step's `StepDefinition.classMap`
8249
+ * applies (Frigate / COCO public catalog). Set on a custom model whose raw
8250
+ * labels already ARE the CamStack macros (Scrypted identity map).
8251
+ */
8252
+ classMap: ClassMapDefinitionSchema.optional()
8229
8253
  });
8230
8254
  var ConvertTargetSchema = discriminatedUnion("format", [object({
8231
8255
  format: literal("openvino"),
@@ -8254,7 +8278,8 @@ var ModelConvertMetadataSchema = object({
8254
8278
  "ocr",
8255
8279
  "segmentation"
8256
8280
  ]),
8257
- faceAlignment: boolean().optional()
8281
+ faceAlignment: boolean().optional(),
8282
+ classMap: ClassMapDefinitionSchema.optional()
8258
8283
  });
8259
8284
  var ConvertResultSchema = object({
8260
8285
  entry: ModelCatalogEntrySchema,
@@ -17564,6 +17589,33 @@ var NativeCropRefSchema = object({
17564
17589
  h: number()
17565
17590
  })
17566
17591
  });
17592
+ object({
17593
+ crop: object({
17594
+ left: number(),
17595
+ top: number(),
17596
+ width: number().positive(),
17597
+ height: number().positive()
17598
+ }).optional(),
17599
+ content: object({
17600
+ width: number().int().positive(),
17601
+ height: number().int().positive()
17602
+ }),
17603
+ fit: _enum(["stretch", "contain"]),
17604
+ format: _enum([
17605
+ "rgb",
17606
+ "gray",
17607
+ "jpeg"
17608
+ ])
17609
+ });
17610
+ var FrameRefSchema = object({
17611
+ registryId: string().min(1),
17612
+ id: string().min(1),
17613
+ width: number().int().positive(),
17614
+ height: number().int().positive(),
17615
+ format: _enum(["rgb", "gray"]),
17616
+ timestamp: number(),
17617
+ capturedAt: number().optional()
17618
+ });
17567
17619
  var ModelFormatSchema$1 = _enum([
17568
17620
  "onnx",
17569
17621
  "coreml",
@@ -17808,6 +17860,12 @@ method(_void(), array(PipelineEngineChoiceSchema)), method(_void(), PipelineEngi
17808
17860
  steps: array(PipelineStepInputSchema).min(1),
17809
17861
  frame: FrameInputSchema.optional(),
17810
17862
  /**
17863
+ * Process-local lazy frame. Valid only when caller and provider resolve
17864
+ * in the same execution-group process; split/cross-node callers use
17865
+ * `frame`/`image` inline compatibility instead.
17866
+ */
17867
+ frameRef: FrameRefSchema.optional(),
17868
+ /**
17811
17869
  * CB5 shm passthrough — a `FrameHandle` naming the same ring slot
17812
17870
  * the decoded pixels live in. One more member of the one-of
17813
17871
  * frame/frameHandle/image/imageBase64/referenceImage group.
@@ -18103,7 +18161,10 @@ var NativeCropResultSchema = object({
18103
18161
  * Which source served this crop, so a quality-sensitive consumer (the native
18104
18162
  * `keyFrame`) can reject a degraded fallback:
18105
18163
  * - `native` — cut from the decode worker's retained NATIVE surface (the
18106
- * quality path).
18164
+ * quality path). A subject-tile serve is also native-resolution and stays
18165
+ * `native` here: the public enum cannot name `tile` without a breaking cap
18166
+ * change. Runner telemetry distinguishes lease vs tile via `source` on the
18167
+ * internal crop result (`nativeHits` vs `tileHits`).
18107
18168
  * - `ram-fullframe` — the native surface MISSED but the request was full-frame,
18108
18169
  * so the ≤640 RAM `RetainedFrameStore` served it (honest lower-res; legit for
18109
18170
  * the blank-frame guard / 640-snapshot resolve, NOT for the clean keyFrame).
@@ -18594,12 +18655,41 @@ var RunnerLocalLoadSchema = object({
18594
18655
  * legacy `OrchestratorMetricsSchema` shape so existing dashboards keep
18595
18656
  * working unchanged when they switch to reading from the runner cap.
18596
18657
  */
18658
+ var FrameLazyCountersSchema = object({
18659
+ framesDecoded: number(),
18660
+ framesAdmitted: number(),
18661
+ framesDroppedPixelFree: number(),
18662
+ viewsMaterialized: number(),
18663
+ viewsSkipped: number(),
18664
+ workerToRunnerBytes: number(),
18665
+ runnerToPoolRawBytes: number(),
18666
+ runnerToPoolJpegBytes: number(),
18667
+ onDemandFullFrameRequests: number(),
18668
+ onDemandCropRequests: number(),
18669
+ nativeHits: number(),
18670
+ nativeMisses: number(),
18671
+ tileHits: number(),
18672
+ tileMisses: number(),
18673
+ fallbackHits: number(),
18674
+ fallbackMisses: number(),
18675
+ retainedWritesAvoided: number(),
18676
+ residentRefs: number(),
18677
+ residentBytes: number(),
18678
+ releases: number(),
18679
+ evictions: number(),
18680
+ staleMisses: number()
18681
+ });
18682
+ var FrameLazyMetricsSchema = object({
18683
+ node: FrameLazyCountersSchema,
18684
+ cameras: array(FrameLazyCountersSchema.extend({ deviceId: number() }))
18685
+ });
18597
18686
  var RunnerLocalMetricsSchema = object({
18598
18687
  nodeId: string(),
18599
18688
  activeCameras: number(),
18600
18689
  throttledCameras: number(),
18601
18690
  avgInferenceTimeMs: number(),
18602
- queueDepth: number()
18691
+ queueDepth: number(),
18692
+ frameLazy: FrameLazyMetricsSchema.optional()
18603
18693
  });
18604
18694
  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({
18605
18695
  handle: FrameHandleSchema,
@@ -19899,6 +19989,9 @@ method(_void(), ProviderInfoSchema), method(object({ config: record(string(), un
19899
19989
  location: StorageLocationSchema,
19900
19990
  relativePath: string()
19901
19991
  }), _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" });
19992
+ /** Profile-exported FormBuilder schema. Shape is ConfigUISchema at the UI. */
19993
+ var ProfileSettingsSchemaBridge = unknown().nullable();
19994
+ var ProfileSettingsBagSchema = record(string(), unknown());
19902
19995
  /**
19903
19996
  * A live terminal session hosted by the provider addon. Output and input do
19904
19997
  * NOT flow through the capability — they use the addon data plane
@@ -19928,7 +20021,14 @@ var TerminalSessionInfoSchema = object({
19928
20021
  var TerminalProfileInfoSchema = object({
19929
20022
  profileId: string(),
19930
20023
  label: string(),
19931
- description: string().optional()
20024
+ description: string().optional(),
20025
+ /** Spawn defaults the instance form copies on create. */
20026
+ executable: string().optional(),
20027
+ args: array(string()).readonly().optional(),
20028
+ cwd: string().optional(),
20029
+ environment: array(string()).readonly().optional(),
20030
+ /** ConfigUISchema for instance knobs, or null when the profile has none. */
20031
+ settingsSchema: ProfileSettingsSchemaBridge.optional()
19932
20032
  });
19933
20033
  /**
19934
20034
  * A durable operator-created Terminal instance. Profiles are templates; only
@@ -19941,7 +20041,12 @@ var TerminalInstanceInfoSchema = object({
19941
20041
  profileId: string(),
19942
20042
  profileLabel: string(),
19943
20043
  name: string(),
19944
- enabled: boolean()
20044
+ enabled: boolean(),
20045
+ executable: string(),
20046
+ args: array(string()).readonly(),
20047
+ cwd: string(),
20048
+ environment: array(string()).readonly(),
20049
+ profileSettings: ProfileSettingsBagSchema
19945
20050
  });
19946
20051
  var TerminalLegacyCameraSchema = object({
19947
20052
  stableId: string(),
@@ -19971,7 +20076,23 @@ var TerminalOutputBatchSchema = object({
19971
20076
  method(_void(), array(TerminalProfileInfoSchema).readonly(), { auth: "admin" }), method(_void(), array(TerminalInstanceInfoSchema).readonly(), { auth: "admin" }), method(object({
19972
20077
  targetNodeId: string().min(1),
19973
20078
  profileId: string().min(1),
19974
- name: string().trim().min(1).max(160).optional()
20079
+ name: string().trim().min(1).max(160).optional(),
20080
+ executable: string().max(1024).optional(),
20081
+ args: array(string().max(2048)).max(64).optional(),
20082
+ cwd: string().max(1024).optional(),
20083
+ environment: array(string().max(4096)).max(64).optional(),
20084
+ profileSettings: ProfileSettingsBagSchema.optional()
20085
+ }), TerminalInstanceInfoSchema, {
20086
+ kind: "mutation",
20087
+ auth: "admin"
20088
+ }), method(object({
20089
+ instanceId: string().min(1),
20090
+ name: string().trim().min(1).max(160).optional(),
20091
+ executable: string().max(1024).optional(),
20092
+ args: array(string().max(2048)).max(64).optional(),
20093
+ cwd: string().max(1024).optional(),
20094
+ environment: array(string().max(4096)).max(64).optional(),
20095
+ profileSettings: ProfileSettingsBagSchema.optional()
19975
20096
  }), TerminalInstanceInfoSchema, {
19976
20097
  kind: "mutation",
19977
20098
  auth: "admin"
@@ -19993,7 +20114,11 @@ method(_void(), array(TerminalProfileInfoSchema).readonly(), { auth: "admin" }),
19993
20114
  }), method(_void(), array(TerminalSessionInfoSchema).readonly(), { auth: "admin" }), method(object({
19994
20115
  profileId: string(),
19995
20116
  cols: number().int().positive(),
19996
- rows: number().int().positive()
20117
+ rows: number().int().positive(),
20118
+ executable: string().max(1024).optional(),
20119
+ args: array(string().max(2048)).max(64).optional(),
20120
+ cwd: string().max(1024).optional(),
20121
+ environment: array(string().max(4096)).max(64).optional()
19997
20122
  }), TerminalSessionInfoSchema, {
19998
20123
  kind: "mutation",
19999
20124
  auth: "admin"
@@ -23876,10 +24001,10 @@ var lawnMowerControlCapability = {
23876
24001
  *
23877
24002
  * • The SDK (mobile / web client) consumes `getConnectionEndpoints()`
23878
24003
  * to receive an ordered list of candidate base URLs it should race
23879
- * on connect — LAN IPv4 first (lowest latency when on same network),
23880
- * then public hostname (if a tunnel is up), then IPv6. The SDK can
23881
- * race them with short timeouts and stick with the winner for the
23882
- * session.
24004
+ * on connect — LAN IPv4 and stable LAN IPv6 first (lowest latency
24005
+ * when on the same network), then public hostname (if a tunnel is
24006
+ * up). The SDK can race them with short timeouts and stick with the
24007
+ * winner for the session.
23883
24008
  *
23884
24009
  * Why hub-only: agents are not directly addressable by the operator's
23885
24010
  * clients — they reverse-connect to the hub. Exposing their interfaces
@@ -24034,6 +24159,17 @@ var NotificationEndpointSchema = object({
24034
24159
  /** What the ranking currently resolves to (null when nothing is reachable). */
24035
24160
  resolved: string().nullable()
24036
24161
  });
24162
+ /**
24163
+ * The URLs the SDK / viewer should race for API access. `baseUrls` empty =
24164
+ * AUTO (every LAN IPv4 + the public tunnel). `resolved` is what that choice
24165
+ * currently expands to, so the UI can show the effective set either way.
24166
+ */
24167
+ var ViewerEndpointsSchema = object({
24168
+ /** The operator's explicit race set, or empty for AUTO. */
24169
+ baseUrls: array(string()).readonly(),
24170
+ /** What the ranking currently resolves to (may be empty if nothing is up). */
24171
+ resolved: array(string()).readonly()
24172
+ });
24037
24173
  var AllowedAddressesSchema = object({
24038
24174
  /**
24039
24175
  * Allowlist of interface addresses operators have explicitly opted
@@ -24042,6 +24178,20 @@ var AllowedAddressesSchema = object({
24042
24178
  * Network Addresses admin page and persisted by the addon.
24043
24179
  */
24044
24180
  addresses: array(string()).readonly() });
24181
+ var TlsStatusSchema = object({
24182
+ mode: _enum([
24183
+ "generated",
24184
+ "uploaded",
24185
+ "disabled"
24186
+ ]),
24187
+ leafFingerprintSha256: string().nullable(),
24188
+ caFingerprintSha256: string().nullable(),
24189
+ validTo: string().nullable(),
24190
+ sans: array(string()),
24191
+ caCertPem: string().nullable(),
24192
+ reissueError: string().nullable(),
24193
+ restartRequired: boolean()
24194
+ });
24045
24195
  method(_void(), ListResultSchema), method(_void(), PreferredSchema), method(object({
24046
24196
  /**
24047
24197
  * LEGACY HINT — do not send from new code. Kept optional so clients
@@ -24051,17 +24201,31 @@ method(_void(), ListResultSchema), method(_void(), PreferredSchema), method(obje
24051
24201
  */
24052
24202
  port: number().int().min(1).max(65535).optional(),
24053
24203
  /** Include `http(s)://127.0.0.1:<port>` as the lowest-priority
24054
- * candidate. Default `true`. */
24204
+ * candidate. Default `false` — loopback is not a client route. */
24055
24205
  includeLoopback: boolean().optional(),
24056
- /** Skip IPv6 entries. Some legacy clients can't parse them.
24057
- * Default `false`. */
24206
+ /** Skip IPv6 entries. Default `false` the palette includes stable
24207
+ * LAN IPv6 (ICE/WebRTC uses dual-stack regardless). Pass `true` to
24208
+ * hide them. The viewer HTTP/WS race is `getViewerEndpoints`. */
24058
24209
  ipv4Only: boolean().optional(),
24059
24210
  /** Scheme to emit for LAN/loopback URLs. Default `'http'`.
24060
24211
  * Pass `'https'` when the caller is itself loaded over HTTPS
24061
24212
  * to avoid mixed-content blocks in the browser. The public
24062
24213
  * tunnel always emits `https://` regardless. */
24063
24214
  scheme: _enum(["http", "https"]).optional()
24064
- }), 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" });
24215
+ }), 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, {
24216
+ kind: "mutation",
24217
+ auth: "admin"
24218
+ }), method(object({
24219
+ certPem: string().min(1),
24220
+ keyPem: string().min(1),
24221
+ caPem: string().optional()
24222
+ }), TlsStatusSchema, {
24223
+ kind: "mutation",
24224
+ auth: "admin"
24225
+ }), method(_void(), object({ pem: string() })), method(_void(), TlsStatusSchema, {
24226
+ kind: "mutation",
24227
+ auth: "admin"
24228
+ });
24065
24229
  var LockControlStatusSchema = object({
24066
24230
  /** Lifecycle state of the lock. `jammed` means the motor reported
24067
24231
  * failure to reach the target — operator intervention required. */
@@ -32088,6 +32252,12 @@ Object.freeze({
32088
32252
  addonId: null,
32089
32253
  access: "create"
32090
32254
  },
32255
+ "localNetwork.downloadCa": {
32256
+ capName: "local-network",
32257
+ capScope: "system",
32258
+ addonId: null,
32259
+ access: "view"
32260
+ },
32091
32261
  "localNetwork.getAllowedAddresses": {
32092
32262
  capName: "local-network",
32093
32263
  capScope: "system",
@@ -32112,18 +32282,42 @@ Object.freeze({
32112
32282
  addonId: null,
32113
32283
  access: "view"
32114
32284
  },
32285
+ "localNetwork.getTlsStatus": {
32286
+ capName: "local-network",
32287
+ capScope: "system",
32288
+ addonId: null,
32289
+ access: "view"
32290
+ },
32291
+ "localNetwork.getViewerEndpoints": {
32292
+ capName: "local-network",
32293
+ capScope: "system",
32294
+ addonId: null,
32295
+ access: "view"
32296
+ },
32115
32297
  "localNetwork.list": {
32116
32298
  capName: "local-network",
32117
32299
  capScope: "system",
32118
32300
  addonId: null,
32119
32301
  access: "view"
32120
32302
  },
32303
+ "localNetwork.regenerateCertificate": {
32304
+ capName: "local-network",
32305
+ capScope: "system",
32306
+ addonId: null,
32307
+ access: "create"
32308
+ },
32121
32309
  "localNetwork.resetAllowlistToBestMatch": {
32122
32310
  capName: "local-network",
32123
32311
  capScope: "system",
32124
32312
  addonId: null,
32125
32313
  access: "delete"
32126
32314
  },
32315
+ "localNetwork.revertToGeneratedCertificate": {
32316
+ capName: "local-network",
32317
+ capScope: "system",
32318
+ addonId: null,
32319
+ access: "create"
32320
+ },
32127
32321
  "localNetwork.setAllowedAddresses": {
32128
32322
  capName: "local-network",
32129
32323
  capScope: "system",
@@ -32136,6 +32330,18 @@ Object.freeze({
32136
32330
  addonId: null,
32137
32331
  access: "create"
32138
32332
  },
32333
+ "localNetwork.setViewerEndpoints": {
32334
+ capName: "local-network",
32335
+ capScope: "system",
32336
+ addonId: null,
32337
+ access: "create"
32338
+ },
32339
+ "localNetwork.uploadCertificate": {
32340
+ capName: "local-network",
32341
+ capScope: "system",
32342
+ addonId: null,
32343
+ access: "create"
32344
+ },
32139
32345
  "lockControl.lock": {
32140
32346
  capName: "lock-control",
32141
32347
  capScope: "device",
@@ -35016,6 +35222,12 @@ Object.freeze({
35016
35222
  addonId: null,
35017
35223
  access: "create"
35018
35224
  },
35225
+ "terminalSession.updateInstance": {
35226
+ capName: "terminal-session",
35227
+ capScope: "system",
35228
+ addonId: null,
35229
+ access: "create"
35230
+ },
35019
35231
  "terminalSession.writeInput": {
35020
35232
  capName: "terminal-session",
35021
35233
  capScope: "system",
@@ -37503,6 +37715,35 @@ Object.freeze(Object.fromEntries([{
37503
37715
  }]
37504
37716
  }].map((s) => [s.stepId, s.defaultModelId])));
37505
37717
  string().min(1);
37718
+ var CLUSTER_STEP_SETTING_FIELDS = [{
37719
+ stepId: "face-embedding",
37720
+ key: "minLandmarkFaceSize",
37721
+ label: "Min face size for recognition (detection px)",
37722
+ 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.",
37723
+ type: "slider",
37724
+ min: 0,
37725
+ max: 64,
37726
+ step: 2,
37727
+ default: 24
37728
+ }];
37729
+ function clusterStepSettingKey(stepId, fieldKey) {
37730
+ return `clusterStepSetting:${stepId}:${fieldKey}`;
37731
+ }
37732
+ var ClusterSettingNumberSchema = number().finite();
37733
+ function readClusterStepSettings(config) {
37734
+ const out = {};
37735
+ for (const field of CLUSTER_STEP_SETTING_FIELDS) {
37736
+ const parsed = ClusterSettingNumberSchema.safeParse(config[clusterStepSettingKey(field.stepId, field.key)]);
37737
+ const value = parsed.success ? parsed.data : field.default;
37738
+ const existing = out[field.stepId] ?? {};
37739
+ out[field.stepId] = {
37740
+ ...existing,
37741
+ [field.key]: value
37742
+ };
37743
+ }
37744
+ return out;
37745
+ }
37746
+ readClusterStepSettings({});
37506
37747
  object({
37507
37748
  /**
37508
37749
  * Fraction of the box's own size added on EACH side before cutting.
package/dist/addon.mjs CHANGED
@@ -36,7 +36,7 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
36
36
  var __toCommonJS = (mod) => __hasOwnProp.call(mod, "module.exports") ? mod["module.exports"] : __copyProps(__defProp({}, "__esModule", { value: true }), mod);
37
37
  var __require = /* @__PURE__ */ createRequire(import.meta.url);
38
38
  //#endregion
39
- //#region ../types/dist/event-category-XfKNtfCc.mjs
39
+ //#region ../types/dist/event-category-CIa_iT6b.mjs
40
40
  var EventCategory = /* @__PURE__ */ function(EventCategory) {
41
41
  EventCategory["SystemBoot"] = "system.boot";
42
42
  EventCategory["SystemAddonsReady"] = "system.addons-ready";
@@ -52,6 +52,15 @@ var EventCategory = /* @__PURE__ */ function(EventCategory) {
52
52
  */
53
53
  EventCategory["SystemRestartCompleted"] = "system.restart-completed";
54
54
  /**
55
+ * The hub reissued its own TLS certificate at boot (`ensureTlsCert`).
56
+ * Emitted only when the material on disk actually changed, so an
57
+ * operator who trusted the old certificate by hand is told rather than
58
+ * discovering it as a browser error. Payload `TlsCertChangedPayload`.
59
+ *
60
+ * Rule: docs/decisions/adr-0227-*.md
61
+ */
62
+ EventCategory["SystemTlsCertChanged"] = "system.tls-cert-changed";
63
+ /**
55
64
  * A newer addon or server-root package version was found by the
56
65
  * authoritative registry check. Emitted once when any observed
57
66
  * `latestVersion` changes (or a package/node first appears behind);
@@ -8048,6 +8057,15 @@ var LabelDefinitionSchema = object({
8048
8057
  description: string().optional(),
8049
8058
  icon: string().optional()
8050
8059
  });
8060
+ var ClassMapDefinitionSchema = object({
8061
+ mapping: record(string(), _enum([
8062
+ "person",
8063
+ "vehicle",
8064
+ "animal",
8065
+ "package"
8066
+ ])),
8067
+ preserveOriginal: boolean()
8068
+ });
8051
8069
  var MODEL_FORMATS = [
8052
8070
  "onnx",
8053
8071
  "coreml",
@@ -8226,7 +8244,13 @@ var ModelCatalogEntrySchema = object({
8226
8244
  * `id` stays the source of truth for resolution/download/persistence; grouping
8227
8245
  * is a presentation overlay resolved back to an `id`.
8228
8246
  */
8229
- group: ModelVariantGroupSchema.optional()
8247
+ group: ModelVariantGroupSchema.optional(),
8248
+ /**
8249
+ * Per-MODEL class map override. Absent ⇒ the step's `StepDefinition.classMap`
8250
+ * applies (Frigate / COCO public catalog). Set on a custom model whose raw
8251
+ * labels already ARE the CamStack macros (Scrypted identity map).
8252
+ */
8253
+ classMap: ClassMapDefinitionSchema.optional()
8230
8254
  });
8231
8255
  var ConvertTargetSchema = discriminatedUnion("format", [object({
8232
8256
  format: literal("openvino"),
@@ -8255,7 +8279,8 @@ var ModelConvertMetadataSchema = object({
8255
8279
  "ocr",
8256
8280
  "segmentation"
8257
8281
  ]),
8258
- faceAlignment: boolean().optional()
8282
+ faceAlignment: boolean().optional(),
8283
+ classMap: ClassMapDefinitionSchema.optional()
8259
8284
  });
8260
8285
  var ConvertResultSchema = object({
8261
8286
  entry: ModelCatalogEntrySchema,
@@ -17565,6 +17590,33 @@ var NativeCropRefSchema = object({
17565
17590
  h: number()
17566
17591
  })
17567
17592
  });
17593
+ object({
17594
+ crop: object({
17595
+ left: number(),
17596
+ top: number(),
17597
+ width: number().positive(),
17598
+ height: number().positive()
17599
+ }).optional(),
17600
+ content: object({
17601
+ width: number().int().positive(),
17602
+ height: number().int().positive()
17603
+ }),
17604
+ fit: _enum(["stretch", "contain"]),
17605
+ format: _enum([
17606
+ "rgb",
17607
+ "gray",
17608
+ "jpeg"
17609
+ ])
17610
+ });
17611
+ var FrameRefSchema = object({
17612
+ registryId: string().min(1),
17613
+ id: string().min(1),
17614
+ width: number().int().positive(),
17615
+ height: number().int().positive(),
17616
+ format: _enum(["rgb", "gray"]),
17617
+ timestamp: number(),
17618
+ capturedAt: number().optional()
17619
+ });
17568
17620
  var ModelFormatSchema$1 = _enum([
17569
17621
  "onnx",
17570
17622
  "coreml",
@@ -17809,6 +17861,12 @@ method(_void(), array(PipelineEngineChoiceSchema)), method(_void(), PipelineEngi
17809
17861
  steps: array(PipelineStepInputSchema).min(1),
17810
17862
  frame: FrameInputSchema.optional(),
17811
17863
  /**
17864
+ * Process-local lazy frame. Valid only when caller and provider resolve
17865
+ * in the same execution-group process; split/cross-node callers use
17866
+ * `frame`/`image` inline compatibility instead.
17867
+ */
17868
+ frameRef: FrameRefSchema.optional(),
17869
+ /**
17812
17870
  * CB5 shm passthrough — a `FrameHandle` naming the same ring slot
17813
17871
  * the decoded pixels live in. One more member of the one-of
17814
17872
  * frame/frameHandle/image/imageBase64/referenceImage group.
@@ -18104,7 +18162,10 @@ var NativeCropResultSchema = object({
18104
18162
  * Which source served this crop, so a quality-sensitive consumer (the native
18105
18163
  * `keyFrame`) can reject a degraded fallback:
18106
18164
  * - `native` — cut from the decode worker's retained NATIVE surface (the
18107
- * quality path).
18165
+ * quality path). A subject-tile serve is also native-resolution and stays
18166
+ * `native` here: the public enum cannot name `tile` without a breaking cap
18167
+ * change. Runner telemetry distinguishes lease vs tile via `source` on the
18168
+ * internal crop result (`nativeHits` vs `tileHits`).
18108
18169
  * - `ram-fullframe` — the native surface MISSED but the request was full-frame,
18109
18170
  * so the ≤640 RAM `RetainedFrameStore` served it (honest lower-res; legit for
18110
18171
  * the blank-frame guard / 640-snapshot resolve, NOT for the clean keyFrame).
@@ -18595,12 +18656,41 @@ var RunnerLocalLoadSchema = object({
18595
18656
  * legacy `OrchestratorMetricsSchema` shape so existing dashboards keep
18596
18657
  * working unchanged when they switch to reading from the runner cap.
18597
18658
  */
18659
+ var FrameLazyCountersSchema = object({
18660
+ framesDecoded: number(),
18661
+ framesAdmitted: number(),
18662
+ framesDroppedPixelFree: number(),
18663
+ viewsMaterialized: number(),
18664
+ viewsSkipped: number(),
18665
+ workerToRunnerBytes: number(),
18666
+ runnerToPoolRawBytes: number(),
18667
+ runnerToPoolJpegBytes: number(),
18668
+ onDemandFullFrameRequests: number(),
18669
+ onDemandCropRequests: number(),
18670
+ nativeHits: number(),
18671
+ nativeMisses: number(),
18672
+ tileHits: number(),
18673
+ tileMisses: number(),
18674
+ fallbackHits: number(),
18675
+ fallbackMisses: number(),
18676
+ retainedWritesAvoided: number(),
18677
+ residentRefs: number(),
18678
+ residentBytes: number(),
18679
+ releases: number(),
18680
+ evictions: number(),
18681
+ staleMisses: number()
18682
+ });
18683
+ var FrameLazyMetricsSchema = object({
18684
+ node: FrameLazyCountersSchema,
18685
+ cameras: array(FrameLazyCountersSchema.extend({ deviceId: number() }))
18686
+ });
18598
18687
  var RunnerLocalMetricsSchema = object({
18599
18688
  nodeId: string(),
18600
18689
  activeCameras: number(),
18601
18690
  throttledCameras: number(),
18602
18691
  avgInferenceTimeMs: number(),
18603
- queueDepth: number()
18692
+ queueDepth: number(),
18693
+ frameLazy: FrameLazyMetricsSchema.optional()
18604
18694
  });
18605
18695
  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({
18606
18696
  handle: FrameHandleSchema,
@@ -19900,6 +19990,9 @@ method(_void(), ProviderInfoSchema), method(object({ config: record(string(), un
19900
19990
  location: StorageLocationSchema,
19901
19991
  relativePath: string()
19902
19992
  }), _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" });
19993
+ /** Profile-exported FormBuilder schema. Shape is ConfigUISchema at the UI. */
19994
+ var ProfileSettingsSchemaBridge = unknown().nullable();
19995
+ var ProfileSettingsBagSchema = record(string(), unknown());
19903
19996
  /**
19904
19997
  * A live terminal session hosted by the provider addon. Output and input do
19905
19998
  * NOT flow through the capability — they use the addon data plane
@@ -19929,7 +20022,14 @@ var TerminalSessionInfoSchema = object({
19929
20022
  var TerminalProfileInfoSchema = object({
19930
20023
  profileId: string(),
19931
20024
  label: string(),
19932
- description: string().optional()
20025
+ description: string().optional(),
20026
+ /** Spawn defaults the instance form copies on create. */
20027
+ executable: string().optional(),
20028
+ args: array(string()).readonly().optional(),
20029
+ cwd: string().optional(),
20030
+ environment: array(string()).readonly().optional(),
20031
+ /** ConfigUISchema for instance knobs, or null when the profile has none. */
20032
+ settingsSchema: ProfileSettingsSchemaBridge.optional()
19933
20033
  });
19934
20034
  /**
19935
20035
  * A durable operator-created Terminal instance. Profiles are templates; only
@@ -19942,7 +20042,12 @@ var TerminalInstanceInfoSchema = object({
19942
20042
  profileId: string(),
19943
20043
  profileLabel: string(),
19944
20044
  name: string(),
19945
- enabled: boolean()
20045
+ enabled: boolean(),
20046
+ executable: string(),
20047
+ args: array(string()).readonly(),
20048
+ cwd: string(),
20049
+ environment: array(string()).readonly(),
20050
+ profileSettings: ProfileSettingsBagSchema
19946
20051
  });
19947
20052
  var TerminalLegacyCameraSchema = object({
19948
20053
  stableId: string(),
@@ -19972,7 +20077,23 @@ var TerminalOutputBatchSchema = object({
19972
20077
  method(_void(), array(TerminalProfileInfoSchema).readonly(), { auth: "admin" }), method(_void(), array(TerminalInstanceInfoSchema).readonly(), { auth: "admin" }), method(object({
19973
20078
  targetNodeId: string().min(1),
19974
20079
  profileId: string().min(1),
19975
- name: string().trim().min(1).max(160).optional()
20080
+ name: string().trim().min(1).max(160).optional(),
20081
+ executable: string().max(1024).optional(),
20082
+ args: array(string().max(2048)).max(64).optional(),
20083
+ cwd: string().max(1024).optional(),
20084
+ environment: array(string().max(4096)).max(64).optional(),
20085
+ profileSettings: ProfileSettingsBagSchema.optional()
20086
+ }), TerminalInstanceInfoSchema, {
20087
+ kind: "mutation",
20088
+ auth: "admin"
20089
+ }), method(object({
20090
+ instanceId: string().min(1),
20091
+ name: string().trim().min(1).max(160).optional(),
20092
+ executable: string().max(1024).optional(),
20093
+ args: array(string().max(2048)).max(64).optional(),
20094
+ cwd: string().max(1024).optional(),
20095
+ environment: array(string().max(4096)).max(64).optional(),
20096
+ profileSettings: ProfileSettingsBagSchema.optional()
19976
20097
  }), TerminalInstanceInfoSchema, {
19977
20098
  kind: "mutation",
19978
20099
  auth: "admin"
@@ -19994,7 +20115,11 @@ method(_void(), array(TerminalProfileInfoSchema).readonly(), { auth: "admin" }),
19994
20115
  }), method(_void(), array(TerminalSessionInfoSchema).readonly(), { auth: "admin" }), method(object({
19995
20116
  profileId: string(),
19996
20117
  cols: number().int().positive(),
19997
- rows: number().int().positive()
20118
+ rows: number().int().positive(),
20119
+ executable: string().max(1024).optional(),
20120
+ args: array(string().max(2048)).max(64).optional(),
20121
+ cwd: string().max(1024).optional(),
20122
+ environment: array(string().max(4096)).max(64).optional()
19998
20123
  }), TerminalSessionInfoSchema, {
19999
20124
  kind: "mutation",
20000
20125
  auth: "admin"
@@ -23877,10 +24002,10 @@ var lawnMowerControlCapability = {
23877
24002
  *
23878
24003
  * • The SDK (mobile / web client) consumes `getConnectionEndpoints()`
23879
24004
  * to receive an ordered list of candidate base URLs it should race
23880
- * on connect — LAN IPv4 first (lowest latency when on same network),
23881
- * then public hostname (if a tunnel is up), then IPv6. The SDK can
23882
- * race them with short timeouts and stick with the winner for the
23883
- * session.
24005
+ * on connect — LAN IPv4 and stable LAN IPv6 first (lowest latency
24006
+ * when on the same network), then public hostname (if a tunnel is
24007
+ * up). The SDK can race them with short timeouts and stick with the
24008
+ * winner for the session.
23884
24009
  *
23885
24010
  * Why hub-only: agents are not directly addressable by the operator's
23886
24011
  * clients — they reverse-connect to the hub. Exposing their interfaces
@@ -24035,6 +24160,17 @@ var NotificationEndpointSchema = object({
24035
24160
  /** What the ranking currently resolves to (null when nothing is reachable). */
24036
24161
  resolved: string().nullable()
24037
24162
  });
24163
+ /**
24164
+ * The URLs the SDK / viewer should race for API access. `baseUrls` empty =
24165
+ * AUTO (every LAN IPv4 + the public tunnel). `resolved` is what that choice
24166
+ * currently expands to, so the UI can show the effective set either way.
24167
+ */
24168
+ var ViewerEndpointsSchema = object({
24169
+ /** The operator's explicit race set, or empty for AUTO. */
24170
+ baseUrls: array(string()).readonly(),
24171
+ /** What the ranking currently resolves to (may be empty if nothing is up). */
24172
+ resolved: array(string()).readonly()
24173
+ });
24038
24174
  var AllowedAddressesSchema = object({
24039
24175
  /**
24040
24176
  * Allowlist of interface addresses operators have explicitly opted
@@ -24043,6 +24179,20 @@ var AllowedAddressesSchema = object({
24043
24179
  * Network Addresses admin page and persisted by the addon.
24044
24180
  */
24045
24181
  addresses: array(string()).readonly() });
24182
+ var TlsStatusSchema = object({
24183
+ mode: _enum([
24184
+ "generated",
24185
+ "uploaded",
24186
+ "disabled"
24187
+ ]),
24188
+ leafFingerprintSha256: string().nullable(),
24189
+ caFingerprintSha256: string().nullable(),
24190
+ validTo: string().nullable(),
24191
+ sans: array(string()),
24192
+ caCertPem: string().nullable(),
24193
+ reissueError: string().nullable(),
24194
+ restartRequired: boolean()
24195
+ });
24046
24196
  method(_void(), ListResultSchema), method(_void(), PreferredSchema), method(object({
24047
24197
  /**
24048
24198
  * LEGACY HINT — do not send from new code. Kept optional so clients
@@ -24052,17 +24202,31 @@ method(_void(), ListResultSchema), method(_void(), PreferredSchema), method(obje
24052
24202
  */
24053
24203
  port: number().int().min(1).max(65535).optional(),
24054
24204
  /** Include `http(s)://127.0.0.1:<port>` as the lowest-priority
24055
- * candidate. Default `true`. */
24205
+ * candidate. Default `false` — loopback is not a client route. */
24056
24206
  includeLoopback: boolean().optional(),
24057
- /** Skip IPv6 entries. Some legacy clients can't parse them.
24058
- * Default `false`. */
24207
+ /** Skip IPv6 entries. Default `false` the palette includes stable
24208
+ * LAN IPv6 (ICE/WebRTC uses dual-stack regardless). Pass `true` to
24209
+ * hide them. The viewer HTTP/WS race is `getViewerEndpoints`. */
24059
24210
  ipv4Only: boolean().optional(),
24060
24211
  /** Scheme to emit for LAN/loopback URLs. Default `'http'`.
24061
24212
  * Pass `'https'` when the caller is itself loaded over HTTPS
24062
24213
  * to avoid mixed-content blocks in the browser. The public
24063
24214
  * tunnel always emits `https://` regardless. */
24064
24215
  scheme: _enum(["http", "https"]).optional()
24065
- }), 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" });
24216
+ }), 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, {
24217
+ kind: "mutation",
24218
+ auth: "admin"
24219
+ }), method(object({
24220
+ certPem: string().min(1),
24221
+ keyPem: string().min(1),
24222
+ caPem: string().optional()
24223
+ }), TlsStatusSchema, {
24224
+ kind: "mutation",
24225
+ auth: "admin"
24226
+ }), method(_void(), object({ pem: string() })), method(_void(), TlsStatusSchema, {
24227
+ kind: "mutation",
24228
+ auth: "admin"
24229
+ });
24066
24230
  var LockControlStatusSchema = object({
24067
24231
  /** Lifecycle state of the lock. `jammed` means the motor reported
24068
24232
  * failure to reach the target — operator intervention required. */
@@ -32089,6 +32253,12 @@ Object.freeze({
32089
32253
  addonId: null,
32090
32254
  access: "create"
32091
32255
  },
32256
+ "localNetwork.downloadCa": {
32257
+ capName: "local-network",
32258
+ capScope: "system",
32259
+ addonId: null,
32260
+ access: "view"
32261
+ },
32092
32262
  "localNetwork.getAllowedAddresses": {
32093
32263
  capName: "local-network",
32094
32264
  capScope: "system",
@@ -32113,18 +32283,42 @@ Object.freeze({
32113
32283
  addonId: null,
32114
32284
  access: "view"
32115
32285
  },
32286
+ "localNetwork.getTlsStatus": {
32287
+ capName: "local-network",
32288
+ capScope: "system",
32289
+ addonId: null,
32290
+ access: "view"
32291
+ },
32292
+ "localNetwork.getViewerEndpoints": {
32293
+ capName: "local-network",
32294
+ capScope: "system",
32295
+ addonId: null,
32296
+ access: "view"
32297
+ },
32116
32298
  "localNetwork.list": {
32117
32299
  capName: "local-network",
32118
32300
  capScope: "system",
32119
32301
  addonId: null,
32120
32302
  access: "view"
32121
32303
  },
32304
+ "localNetwork.regenerateCertificate": {
32305
+ capName: "local-network",
32306
+ capScope: "system",
32307
+ addonId: null,
32308
+ access: "create"
32309
+ },
32122
32310
  "localNetwork.resetAllowlistToBestMatch": {
32123
32311
  capName: "local-network",
32124
32312
  capScope: "system",
32125
32313
  addonId: null,
32126
32314
  access: "delete"
32127
32315
  },
32316
+ "localNetwork.revertToGeneratedCertificate": {
32317
+ capName: "local-network",
32318
+ capScope: "system",
32319
+ addonId: null,
32320
+ access: "create"
32321
+ },
32128
32322
  "localNetwork.setAllowedAddresses": {
32129
32323
  capName: "local-network",
32130
32324
  capScope: "system",
@@ -32137,6 +32331,18 @@ Object.freeze({
32137
32331
  addonId: null,
32138
32332
  access: "create"
32139
32333
  },
32334
+ "localNetwork.setViewerEndpoints": {
32335
+ capName: "local-network",
32336
+ capScope: "system",
32337
+ addonId: null,
32338
+ access: "create"
32339
+ },
32340
+ "localNetwork.uploadCertificate": {
32341
+ capName: "local-network",
32342
+ capScope: "system",
32343
+ addonId: null,
32344
+ access: "create"
32345
+ },
32140
32346
  "lockControl.lock": {
32141
32347
  capName: "lock-control",
32142
32348
  capScope: "device",
@@ -35017,6 +35223,12 @@ Object.freeze({
35017
35223
  addonId: null,
35018
35224
  access: "create"
35019
35225
  },
35226
+ "terminalSession.updateInstance": {
35227
+ capName: "terminal-session",
35228
+ capScope: "system",
35229
+ addonId: null,
35230
+ access: "create"
35231
+ },
35020
35232
  "terminalSession.writeInput": {
35021
35233
  capName: "terminal-session",
35022
35234
  capScope: "system",
@@ -37504,6 +37716,35 @@ Object.freeze(Object.fromEntries([{
37504
37716
  }]
37505
37717
  }].map((s) => [s.stepId, s.defaultModelId])));
37506
37718
  string().min(1);
37719
+ var CLUSTER_STEP_SETTING_FIELDS = [{
37720
+ stepId: "face-embedding",
37721
+ key: "minLandmarkFaceSize",
37722
+ label: "Min face size for recognition (detection px)",
37723
+ 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.",
37724
+ type: "slider",
37725
+ min: 0,
37726
+ max: 64,
37727
+ step: 2,
37728
+ default: 24
37729
+ }];
37730
+ function clusterStepSettingKey(stepId, fieldKey) {
37731
+ return `clusterStepSetting:${stepId}:${fieldKey}`;
37732
+ }
37733
+ var ClusterSettingNumberSchema = number().finite();
37734
+ function readClusterStepSettings(config) {
37735
+ const out = {};
37736
+ for (const field of CLUSTER_STEP_SETTING_FIELDS) {
37737
+ const parsed = ClusterSettingNumberSchema.safeParse(config[clusterStepSettingKey(field.stepId, field.key)]);
37738
+ const value = parsed.success ? parsed.data : field.default;
37739
+ const existing = out[field.stepId] ?? {};
37740
+ out[field.stepId] = {
37741
+ ...existing,
37742
+ [field.key]: value
37743
+ };
37744
+ }
37745
+ return out;
37746
+ }
37747
+ readClusterStepSettings({});
37507
37748
  object({
37508
37749
  /**
37509
37750
  * 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-dreo",
3
- "version": "0.2.25",
3
+ "version": "0.2.27",
4
4
  "description": "Dreo smart-device (fan / air-circulator / purifier / heater / humidifier) device-provider addon for CamStack — wraps the @apocaliss92/nodedreo Dreo cloud client (REST + WebSocket)",
5
5
  "keywords": [
6
6
  "camstack",