@camstack/addon-provider-homematic 1.2.26 → 1.2.28

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
@@ -8,7 +8,7 @@ let http = require("http");
8
8
  let crypto$1 = require("crypto");
9
9
  let fs_promises = require("fs/promises");
10
10
  let path = require("path");
11
- //#region ../types/dist/event-category-XfKNtfCc.mjs
11
+ //#region ../types/dist/event-category-CIa_iT6b.mjs
12
12
  var EventCategory = /* @__PURE__ */ function(EventCategory) {
13
13
  EventCategory["SystemBoot"] = "system.boot";
14
14
  EventCategory["SystemAddonsReady"] = "system.addons-ready";
@@ -24,6 +24,15 @@ var EventCategory = /* @__PURE__ */ function(EventCategory) {
24
24
  */
25
25
  EventCategory["SystemRestartCompleted"] = "system.restart-completed";
26
26
  /**
27
+ * The hub reissued its own TLS certificate at boot (`ensureTlsCert`).
28
+ * Emitted only when the material on disk actually changed, so an
29
+ * operator who trusted the old certificate by hand is told rather than
30
+ * discovering it as a browser error. Payload `TlsCertChangedPayload`.
31
+ *
32
+ * Rule: docs/decisions/adr-0227-*.md
33
+ */
34
+ EventCategory["SystemTlsCertChanged"] = "system.tls-cert-changed";
35
+ /**
27
36
  * A newer addon or server-root package version was found by the
28
37
  * authoritative registry check. Emitted once when any observed
29
38
  * `latestVersion` changes (or a package/node first appears behind);
@@ -8020,6 +8029,15 @@ var LabelDefinitionSchema = object({
8020
8029
  description: string().optional(),
8021
8030
  icon: string().optional()
8022
8031
  });
8032
+ var ClassMapDefinitionSchema = object({
8033
+ mapping: record(string(), _enum([
8034
+ "person",
8035
+ "vehicle",
8036
+ "animal",
8037
+ "package"
8038
+ ])),
8039
+ preserveOriginal: boolean()
8040
+ });
8023
8041
  var MODEL_FORMATS = [
8024
8042
  "onnx",
8025
8043
  "coreml",
@@ -8198,7 +8216,13 @@ var ModelCatalogEntrySchema = object({
8198
8216
  * `id` stays the source of truth for resolution/download/persistence; grouping
8199
8217
  * is a presentation overlay resolved back to an `id`.
8200
8218
  */
8201
- group: ModelVariantGroupSchema.optional()
8219
+ group: ModelVariantGroupSchema.optional(),
8220
+ /**
8221
+ * Per-MODEL class map override. Absent ⇒ the step's `StepDefinition.classMap`
8222
+ * applies (Frigate / COCO public catalog). Set on a custom model whose raw
8223
+ * labels already ARE the CamStack macros (Scrypted identity map).
8224
+ */
8225
+ classMap: ClassMapDefinitionSchema.optional()
8202
8226
  });
8203
8227
  var ConvertTargetSchema = discriminatedUnion("format", [object({
8204
8228
  format: literal("openvino"),
@@ -8227,7 +8251,8 @@ var ModelConvertMetadataSchema = object({
8227
8251
  "ocr",
8228
8252
  "segmentation"
8229
8253
  ]),
8230
- faceAlignment: boolean().optional()
8254
+ faceAlignment: boolean().optional(),
8255
+ classMap: ClassMapDefinitionSchema.optional()
8231
8256
  });
8232
8257
  var ConvertResultSchema = object({
8233
8258
  entry: ModelCatalogEntrySchema,
@@ -17586,6 +17611,33 @@ var NativeCropRefSchema = object({
17586
17611
  h: number()
17587
17612
  })
17588
17613
  });
17614
+ object({
17615
+ crop: object({
17616
+ left: number(),
17617
+ top: number(),
17618
+ width: number().positive(),
17619
+ height: number().positive()
17620
+ }).optional(),
17621
+ content: object({
17622
+ width: number().int().positive(),
17623
+ height: number().int().positive()
17624
+ }),
17625
+ fit: _enum(["stretch", "contain"]),
17626
+ format: _enum([
17627
+ "rgb",
17628
+ "gray",
17629
+ "jpeg"
17630
+ ])
17631
+ });
17632
+ var FrameRefSchema = object({
17633
+ registryId: string().min(1),
17634
+ id: string().min(1),
17635
+ width: number().int().positive(),
17636
+ height: number().int().positive(),
17637
+ format: _enum(["rgb", "gray"]),
17638
+ timestamp: number(),
17639
+ capturedAt: number().optional()
17640
+ });
17589
17641
  var ModelFormatSchema$1 = _enum([
17590
17642
  "onnx",
17591
17643
  "coreml",
@@ -17830,6 +17882,12 @@ method(_void(), array(PipelineEngineChoiceSchema)), method(_void(), PipelineEngi
17830
17882
  steps: array(PipelineStepInputSchema).min(1),
17831
17883
  frame: FrameInputSchema.optional(),
17832
17884
  /**
17885
+ * Process-local lazy frame. Valid only when caller and provider resolve
17886
+ * in the same execution-group process; split/cross-node callers use
17887
+ * `frame`/`image` inline compatibility instead.
17888
+ */
17889
+ frameRef: FrameRefSchema.optional(),
17890
+ /**
17833
17891
  * CB5 shm passthrough — a `FrameHandle` naming the same ring slot
17834
17892
  * the decoded pixels live in. One more member of the one-of
17835
17893
  * frame/frameHandle/image/imageBase64/referenceImage group.
@@ -18125,7 +18183,10 @@ var NativeCropResultSchema = object({
18125
18183
  * Which source served this crop, so a quality-sensitive consumer (the native
18126
18184
  * `keyFrame`) can reject a degraded fallback:
18127
18185
  * - `native` — cut from the decode worker's retained NATIVE surface (the
18128
- * quality path).
18186
+ * quality path). A subject-tile serve is also native-resolution and stays
18187
+ * `native` here: the public enum cannot name `tile` without a breaking cap
18188
+ * change. Runner telemetry distinguishes lease vs tile via `source` on the
18189
+ * internal crop result (`nativeHits` vs `tileHits`).
18129
18190
  * - `ram-fullframe` — the native surface MISSED but the request was full-frame,
18130
18191
  * so the ≤640 RAM `RetainedFrameStore` served it (honest lower-res; legit for
18131
18192
  * the blank-frame guard / 640-snapshot resolve, NOT for the clean keyFrame).
@@ -18616,12 +18677,41 @@ var RunnerLocalLoadSchema = object({
18616
18677
  * legacy `OrchestratorMetricsSchema` shape so existing dashboards keep
18617
18678
  * working unchanged when they switch to reading from the runner cap.
18618
18679
  */
18680
+ var FrameLazyCountersSchema = object({
18681
+ framesDecoded: number(),
18682
+ framesAdmitted: number(),
18683
+ framesDroppedPixelFree: number(),
18684
+ viewsMaterialized: number(),
18685
+ viewsSkipped: number(),
18686
+ workerToRunnerBytes: number(),
18687
+ runnerToPoolRawBytes: number(),
18688
+ runnerToPoolJpegBytes: number(),
18689
+ onDemandFullFrameRequests: number(),
18690
+ onDemandCropRequests: number(),
18691
+ nativeHits: number(),
18692
+ nativeMisses: number(),
18693
+ tileHits: number(),
18694
+ tileMisses: number(),
18695
+ fallbackHits: number(),
18696
+ fallbackMisses: number(),
18697
+ retainedWritesAvoided: number(),
18698
+ residentRefs: number(),
18699
+ residentBytes: number(),
18700
+ releases: number(),
18701
+ evictions: number(),
18702
+ staleMisses: number()
18703
+ });
18704
+ var FrameLazyMetricsSchema = object({
18705
+ node: FrameLazyCountersSchema,
18706
+ cameras: array(FrameLazyCountersSchema.extend({ deviceId: number() }))
18707
+ });
18619
18708
  var RunnerLocalMetricsSchema = object({
18620
18709
  nodeId: string(),
18621
18710
  activeCameras: number(),
18622
18711
  throttledCameras: number(),
18623
18712
  avgInferenceTimeMs: number(),
18624
- queueDepth: number()
18713
+ queueDepth: number(),
18714
+ frameLazy: FrameLazyMetricsSchema.optional()
18625
18715
  });
18626
18716
  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({
18627
18717
  handle: FrameHandleSchema,
@@ -19921,6 +20011,9 @@ method(_void(), ProviderInfoSchema), method(object({ config: record(string(), un
19921
20011
  location: StorageLocationSchema,
19922
20012
  relativePath: string()
19923
20013
  }), _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" });
20014
+ /** Profile-exported FormBuilder schema. Shape is ConfigUISchema at the UI. */
20015
+ var ProfileSettingsSchemaBridge = unknown().nullable();
20016
+ var ProfileSettingsBagSchema = record(string(), unknown());
19924
20017
  /**
19925
20018
  * A live terminal session hosted by the provider addon. Output and input do
19926
20019
  * NOT flow through the capability — they use the addon data plane
@@ -19950,7 +20043,14 @@ var TerminalSessionInfoSchema = object({
19950
20043
  var TerminalProfileInfoSchema = object({
19951
20044
  profileId: string(),
19952
20045
  label: string(),
19953
- description: string().optional()
20046
+ description: string().optional(),
20047
+ /** Spawn defaults the instance form copies on create. */
20048
+ executable: string().optional(),
20049
+ args: array(string()).readonly().optional(),
20050
+ cwd: string().optional(),
20051
+ environment: array(string()).readonly().optional(),
20052
+ /** ConfigUISchema for instance knobs, or null when the profile has none. */
20053
+ settingsSchema: ProfileSettingsSchemaBridge.optional()
19954
20054
  });
19955
20055
  /**
19956
20056
  * A durable operator-created Terminal instance. Profiles are templates; only
@@ -19963,7 +20063,12 @@ var TerminalInstanceInfoSchema = object({
19963
20063
  profileId: string(),
19964
20064
  profileLabel: string(),
19965
20065
  name: string(),
19966
- enabled: boolean()
20066
+ enabled: boolean(),
20067
+ executable: string(),
20068
+ args: array(string()).readonly(),
20069
+ cwd: string(),
20070
+ environment: array(string()).readonly(),
20071
+ profileSettings: ProfileSettingsBagSchema
19967
20072
  });
19968
20073
  var TerminalLegacyCameraSchema = object({
19969
20074
  stableId: string(),
@@ -19993,7 +20098,23 @@ var TerminalOutputBatchSchema = object({
19993
20098
  method(_void(), array(TerminalProfileInfoSchema).readonly(), { auth: "admin" }), method(_void(), array(TerminalInstanceInfoSchema).readonly(), { auth: "admin" }), method(object({
19994
20099
  targetNodeId: string().min(1),
19995
20100
  profileId: string().min(1),
19996
- name: string().trim().min(1).max(160).optional()
20101
+ name: string().trim().min(1).max(160).optional(),
20102
+ executable: string().max(1024).optional(),
20103
+ args: array(string().max(2048)).max(64).optional(),
20104
+ cwd: string().max(1024).optional(),
20105
+ environment: array(string().max(4096)).max(64).optional(),
20106
+ profileSettings: ProfileSettingsBagSchema.optional()
20107
+ }), TerminalInstanceInfoSchema, {
20108
+ kind: "mutation",
20109
+ auth: "admin"
20110
+ }), method(object({
20111
+ instanceId: string().min(1),
20112
+ name: string().trim().min(1).max(160).optional(),
20113
+ executable: string().max(1024).optional(),
20114
+ args: array(string().max(2048)).max(64).optional(),
20115
+ cwd: string().max(1024).optional(),
20116
+ environment: array(string().max(4096)).max(64).optional(),
20117
+ profileSettings: ProfileSettingsBagSchema.optional()
19997
20118
  }), TerminalInstanceInfoSchema, {
19998
20119
  kind: "mutation",
19999
20120
  auth: "admin"
@@ -20015,7 +20136,11 @@ method(_void(), array(TerminalProfileInfoSchema).readonly(), { auth: "admin" }),
20015
20136
  }), method(_void(), array(TerminalSessionInfoSchema).readonly(), { auth: "admin" }), method(object({
20016
20137
  profileId: string(),
20017
20138
  cols: number().int().positive(),
20018
- rows: number().int().positive()
20139
+ rows: number().int().positive(),
20140
+ executable: string().max(1024).optional(),
20141
+ args: array(string().max(2048)).max(64).optional(),
20142
+ cwd: string().max(1024).optional(),
20143
+ environment: array(string().max(4096)).max(64).optional()
20019
20144
  }), TerminalSessionInfoSchema, {
20020
20145
  kind: "mutation",
20021
20146
  auth: "admin"
@@ -23890,10 +24015,10 @@ var lawnMowerControlCapability = {
23890
24015
  *
23891
24016
  * • The SDK (mobile / web client) consumes `getConnectionEndpoints()`
23892
24017
  * to receive an ordered list of candidate base URLs it should race
23893
- * on connect — LAN IPv4 first (lowest latency when on same network),
23894
- * then public hostname (if a tunnel is up), then IPv6. The SDK can
23895
- * race them with short timeouts and stick with the winner for the
23896
- * session.
24018
+ * on connect — LAN IPv4 and stable LAN IPv6 first (lowest latency
24019
+ * when on the same network), then public hostname (if a tunnel is
24020
+ * up). The SDK can race them with short timeouts and stick with the
24021
+ * winner for the session.
23897
24022
  *
23898
24023
  * Why hub-only: agents are not directly addressable by the operator's
23899
24024
  * clients — they reverse-connect to the hub. Exposing their interfaces
@@ -24048,6 +24173,17 @@ var NotificationEndpointSchema = object({
24048
24173
  /** What the ranking currently resolves to (null when nothing is reachable). */
24049
24174
  resolved: string().nullable()
24050
24175
  });
24176
+ /**
24177
+ * The URLs the SDK / viewer should race for API access. `baseUrls` empty =
24178
+ * AUTO (every LAN IPv4 + the public tunnel). `resolved` is what that choice
24179
+ * currently expands to, so the UI can show the effective set either way.
24180
+ */
24181
+ var ViewerEndpointsSchema = object({
24182
+ /** The operator's explicit race set, or empty for AUTO. */
24183
+ baseUrls: array(string()).readonly(),
24184
+ /** What the ranking currently resolves to (may be empty if nothing is up). */
24185
+ resolved: array(string()).readonly()
24186
+ });
24051
24187
  var AllowedAddressesSchema = object({
24052
24188
  /**
24053
24189
  * Allowlist of interface addresses operators have explicitly opted
@@ -24056,6 +24192,20 @@ var AllowedAddressesSchema = object({
24056
24192
  * Network Addresses admin page and persisted by the addon.
24057
24193
  */
24058
24194
  addresses: array(string()).readonly() });
24195
+ var TlsStatusSchema = object({
24196
+ mode: _enum([
24197
+ "generated",
24198
+ "uploaded",
24199
+ "disabled"
24200
+ ]),
24201
+ leafFingerprintSha256: string().nullable(),
24202
+ caFingerprintSha256: string().nullable(),
24203
+ validTo: string().nullable(),
24204
+ sans: array(string()),
24205
+ caCertPem: string().nullable(),
24206
+ reissueError: string().nullable(),
24207
+ restartRequired: boolean()
24208
+ });
24059
24209
  method(_void(), ListResultSchema), method(_void(), PreferredSchema), method(object({
24060
24210
  /**
24061
24211
  * LEGACY HINT — do not send from new code. Kept optional so clients
@@ -24065,17 +24215,31 @@ method(_void(), ListResultSchema), method(_void(), PreferredSchema), method(obje
24065
24215
  */
24066
24216
  port: number().int().min(1).max(65535).optional(),
24067
24217
  /** Include `http(s)://127.0.0.1:<port>` as the lowest-priority
24068
- * candidate. Default `true`. */
24218
+ * candidate. Default `false` — loopback is not a client route. */
24069
24219
  includeLoopback: boolean().optional(),
24070
- /** Skip IPv6 entries. Some legacy clients can't parse them.
24071
- * Default `false`. */
24220
+ /** Skip IPv6 entries. Default `false` — the palette includes stable
24221
+ * LAN IPv6 (ICE/WebRTC uses dual-stack regardless). Pass `true` to
24222
+ * hide them. The viewer HTTP/WS race is `getViewerEndpoints`. */
24072
24223
  ipv4Only: boolean().optional(),
24073
24224
  /** Scheme to emit for LAN/loopback URLs. Default `'http'`.
24074
24225
  * Pass `'https'` when the caller is itself loaded over HTTPS
24075
24226
  * to avoid mixed-content blocks in the browser. The public
24076
24227
  * tunnel always emits `https://` regardless. */
24077
24228
  scheme: _enum(["http", "https"]).optional()
24078
- }), 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" });
24229
+ }), 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, {
24230
+ kind: "mutation",
24231
+ auth: "admin"
24232
+ }), method(object({
24233
+ certPem: string().min(1),
24234
+ keyPem: string().min(1),
24235
+ caPem: string().optional()
24236
+ }), TlsStatusSchema, {
24237
+ kind: "mutation",
24238
+ auth: "admin"
24239
+ }), method(_void(), object({ pem: string() })), method(_void(), TlsStatusSchema, {
24240
+ kind: "mutation",
24241
+ auth: "admin"
24242
+ });
24079
24243
  var LockControlStatusSchema = object({
24080
24244
  /** Lifecycle state of the lock. `jammed` means the motor reported
24081
24245
  * failure to reach the target — operator intervention required. */
@@ -32102,6 +32266,12 @@ Object.freeze({
32102
32266
  addonId: null,
32103
32267
  access: "create"
32104
32268
  },
32269
+ "localNetwork.downloadCa": {
32270
+ capName: "local-network",
32271
+ capScope: "system",
32272
+ addonId: null,
32273
+ access: "view"
32274
+ },
32105
32275
  "localNetwork.getAllowedAddresses": {
32106
32276
  capName: "local-network",
32107
32277
  capScope: "system",
@@ -32126,18 +32296,42 @@ Object.freeze({
32126
32296
  addonId: null,
32127
32297
  access: "view"
32128
32298
  },
32299
+ "localNetwork.getTlsStatus": {
32300
+ capName: "local-network",
32301
+ capScope: "system",
32302
+ addonId: null,
32303
+ access: "view"
32304
+ },
32305
+ "localNetwork.getViewerEndpoints": {
32306
+ capName: "local-network",
32307
+ capScope: "system",
32308
+ addonId: null,
32309
+ access: "view"
32310
+ },
32129
32311
  "localNetwork.list": {
32130
32312
  capName: "local-network",
32131
32313
  capScope: "system",
32132
32314
  addonId: null,
32133
32315
  access: "view"
32134
32316
  },
32317
+ "localNetwork.regenerateCertificate": {
32318
+ capName: "local-network",
32319
+ capScope: "system",
32320
+ addonId: null,
32321
+ access: "create"
32322
+ },
32135
32323
  "localNetwork.resetAllowlistToBestMatch": {
32136
32324
  capName: "local-network",
32137
32325
  capScope: "system",
32138
32326
  addonId: null,
32139
32327
  access: "delete"
32140
32328
  },
32329
+ "localNetwork.revertToGeneratedCertificate": {
32330
+ capName: "local-network",
32331
+ capScope: "system",
32332
+ addonId: null,
32333
+ access: "create"
32334
+ },
32141
32335
  "localNetwork.setAllowedAddresses": {
32142
32336
  capName: "local-network",
32143
32337
  capScope: "system",
@@ -32150,6 +32344,18 @@ Object.freeze({
32150
32344
  addonId: null,
32151
32345
  access: "create"
32152
32346
  },
32347
+ "localNetwork.setViewerEndpoints": {
32348
+ capName: "local-network",
32349
+ capScope: "system",
32350
+ addonId: null,
32351
+ access: "create"
32352
+ },
32353
+ "localNetwork.uploadCertificate": {
32354
+ capName: "local-network",
32355
+ capScope: "system",
32356
+ addonId: null,
32357
+ access: "create"
32358
+ },
32153
32359
  "lockControl.lock": {
32154
32360
  capName: "lock-control",
32155
32361
  capScope: "device",
@@ -35030,6 +35236,12 @@ Object.freeze({
35030
35236
  addonId: null,
35031
35237
  access: "create"
35032
35238
  },
35239
+ "terminalSession.updateInstance": {
35240
+ capName: "terminal-session",
35241
+ capScope: "system",
35242
+ addonId: null,
35243
+ access: "create"
35244
+ },
35033
35245
  "terminalSession.writeInput": {
35034
35246
  capName: "terminal-session",
35035
35247
  capScope: "system",
@@ -37517,6 +37729,35 @@ Object.freeze(Object.fromEntries([{
37517
37729
  }]
37518
37730
  }].map((s) => [s.stepId, s.defaultModelId])));
37519
37731
  string().min(1);
37732
+ var CLUSTER_STEP_SETTING_FIELDS = [{
37733
+ stepId: "face-embedding",
37734
+ key: "minLandmarkFaceSize",
37735
+ label: "Min face size for recognition (detection px)",
37736
+ 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.",
37737
+ type: "slider",
37738
+ min: 0,
37739
+ max: 64,
37740
+ step: 2,
37741
+ default: 24
37742
+ }];
37743
+ function clusterStepSettingKey(stepId, fieldKey) {
37744
+ return `clusterStepSetting:${stepId}:${fieldKey}`;
37745
+ }
37746
+ var ClusterSettingNumberSchema = number().finite();
37747
+ function readClusterStepSettings(config) {
37748
+ const out = {};
37749
+ for (const field of CLUSTER_STEP_SETTING_FIELDS) {
37750
+ const parsed = ClusterSettingNumberSchema.safeParse(config[clusterStepSettingKey(field.stepId, field.key)]);
37751
+ const value = parsed.success ? parsed.data : field.default;
37752
+ const existing = out[field.stepId] ?? {};
37753
+ out[field.stepId] = {
37754
+ ...existing,
37755
+ [field.key]: value
37756
+ };
37757
+ }
37758
+ return out;
37759
+ }
37760
+ readClusterStepSettings({});
37520
37761
  object({
37521
37762
  /**
37522
37763
  * Fraction of the box's own size added on EACH side before cutting.
package/dist/addon.mjs CHANGED
@@ -9,7 +9,7 @@ import { join } from "path";
9
9
  var __commonJSMin = (cb, mod) => () => (mod || (cb((mod = { exports: {} }).exports, mod), cb = null), mod.exports);
10
10
  var __require = /* @__PURE__ */ createRequire(import.meta.url);
11
11
  //#endregion
12
- //#region ../types/dist/event-category-XfKNtfCc.mjs
12
+ //#region ../types/dist/event-category-CIa_iT6b.mjs
13
13
  var EventCategory = /* @__PURE__ */ function(EventCategory) {
14
14
  EventCategory["SystemBoot"] = "system.boot";
15
15
  EventCategory["SystemAddonsReady"] = "system.addons-ready";
@@ -25,6 +25,15 @@ var EventCategory = /* @__PURE__ */ function(EventCategory) {
25
25
  */
26
26
  EventCategory["SystemRestartCompleted"] = "system.restart-completed";
27
27
  /**
28
+ * The hub reissued its own TLS certificate at boot (`ensureTlsCert`).
29
+ * Emitted only when the material on disk actually changed, so an
30
+ * operator who trusted the old certificate by hand is told rather than
31
+ * discovering it as a browser error. Payload `TlsCertChangedPayload`.
32
+ *
33
+ * Rule: docs/decisions/adr-0227-*.md
34
+ */
35
+ EventCategory["SystemTlsCertChanged"] = "system.tls-cert-changed";
36
+ /**
28
37
  * A newer addon or server-root package version was found by the
29
38
  * authoritative registry check. Emitted once when any observed
30
39
  * `latestVersion` changes (or a package/node first appears behind);
@@ -8021,6 +8030,15 @@ var LabelDefinitionSchema = object({
8021
8030
  description: string().optional(),
8022
8031
  icon: string().optional()
8023
8032
  });
8033
+ var ClassMapDefinitionSchema = object({
8034
+ mapping: record(string(), _enum([
8035
+ "person",
8036
+ "vehicle",
8037
+ "animal",
8038
+ "package"
8039
+ ])),
8040
+ preserveOriginal: boolean()
8041
+ });
8024
8042
  var MODEL_FORMATS = [
8025
8043
  "onnx",
8026
8044
  "coreml",
@@ -8199,7 +8217,13 @@ var ModelCatalogEntrySchema = object({
8199
8217
  * `id` stays the source of truth for resolution/download/persistence; grouping
8200
8218
  * is a presentation overlay resolved back to an `id`.
8201
8219
  */
8202
- group: ModelVariantGroupSchema.optional()
8220
+ group: ModelVariantGroupSchema.optional(),
8221
+ /**
8222
+ * Per-MODEL class map override. Absent ⇒ the step's `StepDefinition.classMap`
8223
+ * applies (Frigate / COCO public catalog). Set on a custom model whose raw
8224
+ * labels already ARE the CamStack macros (Scrypted identity map).
8225
+ */
8226
+ classMap: ClassMapDefinitionSchema.optional()
8203
8227
  });
8204
8228
  var ConvertTargetSchema = discriminatedUnion("format", [object({
8205
8229
  format: literal("openvino"),
@@ -8228,7 +8252,8 @@ var ModelConvertMetadataSchema = object({
8228
8252
  "ocr",
8229
8253
  "segmentation"
8230
8254
  ]),
8231
- faceAlignment: boolean().optional()
8255
+ faceAlignment: boolean().optional(),
8256
+ classMap: ClassMapDefinitionSchema.optional()
8232
8257
  });
8233
8258
  var ConvertResultSchema = object({
8234
8259
  entry: ModelCatalogEntrySchema,
@@ -17587,6 +17612,33 @@ var NativeCropRefSchema = object({
17587
17612
  h: number()
17588
17613
  })
17589
17614
  });
17615
+ object({
17616
+ crop: object({
17617
+ left: number(),
17618
+ top: number(),
17619
+ width: number().positive(),
17620
+ height: number().positive()
17621
+ }).optional(),
17622
+ content: object({
17623
+ width: number().int().positive(),
17624
+ height: number().int().positive()
17625
+ }),
17626
+ fit: _enum(["stretch", "contain"]),
17627
+ format: _enum([
17628
+ "rgb",
17629
+ "gray",
17630
+ "jpeg"
17631
+ ])
17632
+ });
17633
+ var FrameRefSchema = object({
17634
+ registryId: string().min(1),
17635
+ id: string().min(1),
17636
+ width: number().int().positive(),
17637
+ height: number().int().positive(),
17638
+ format: _enum(["rgb", "gray"]),
17639
+ timestamp: number(),
17640
+ capturedAt: number().optional()
17641
+ });
17590
17642
  var ModelFormatSchema$1 = _enum([
17591
17643
  "onnx",
17592
17644
  "coreml",
@@ -17831,6 +17883,12 @@ method(_void(), array(PipelineEngineChoiceSchema)), method(_void(), PipelineEngi
17831
17883
  steps: array(PipelineStepInputSchema).min(1),
17832
17884
  frame: FrameInputSchema.optional(),
17833
17885
  /**
17886
+ * Process-local lazy frame. Valid only when caller and provider resolve
17887
+ * in the same execution-group process; split/cross-node callers use
17888
+ * `frame`/`image` inline compatibility instead.
17889
+ */
17890
+ frameRef: FrameRefSchema.optional(),
17891
+ /**
17834
17892
  * CB5 shm passthrough — a `FrameHandle` naming the same ring slot
17835
17893
  * the decoded pixels live in. One more member of the one-of
17836
17894
  * frame/frameHandle/image/imageBase64/referenceImage group.
@@ -18126,7 +18184,10 @@ var NativeCropResultSchema = object({
18126
18184
  * Which source served this crop, so a quality-sensitive consumer (the native
18127
18185
  * `keyFrame`) can reject a degraded fallback:
18128
18186
  * - `native` — cut from the decode worker's retained NATIVE surface (the
18129
- * quality path).
18187
+ * quality path). A subject-tile serve is also native-resolution and stays
18188
+ * `native` here: the public enum cannot name `tile` without a breaking cap
18189
+ * change. Runner telemetry distinguishes lease vs tile via `source` on the
18190
+ * internal crop result (`nativeHits` vs `tileHits`).
18130
18191
  * - `ram-fullframe` — the native surface MISSED but the request was full-frame,
18131
18192
  * so the ≤640 RAM `RetainedFrameStore` served it (honest lower-res; legit for
18132
18193
  * the blank-frame guard / 640-snapshot resolve, NOT for the clean keyFrame).
@@ -18617,12 +18678,41 @@ var RunnerLocalLoadSchema = object({
18617
18678
  * legacy `OrchestratorMetricsSchema` shape so existing dashboards keep
18618
18679
  * working unchanged when they switch to reading from the runner cap.
18619
18680
  */
18681
+ var FrameLazyCountersSchema = object({
18682
+ framesDecoded: number(),
18683
+ framesAdmitted: number(),
18684
+ framesDroppedPixelFree: number(),
18685
+ viewsMaterialized: number(),
18686
+ viewsSkipped: number(),
18687
+ workerToRunnerBytes: number(),
18688
+ runnerToPoolRawBytes: number(),
18689
+ runnerToPoolJpegBytes: number(),
18690
+ onDemandFullFrameRequests: number(),
18691
+ onDemandCropRequests: number(),
18692
+ nativeHits: number(),
18693
+ nativeMisses: number(),
18694
+ tileHits: number(),
18695
+ tileMisses: number(),
18696
+ fallbackHits: number(),
18697
+ fallbackMisses: number(),
18698
+ retainedWritesAvoided: number(),
18699
+ residentRefs: number(),
18700
+ residentBytes: number(),
18701
+ releases: number(),
18702
+ evictions: number(),
18703
+ staleMisses: number()
18704
+ });
18705
+ var FrameLazyMetricsSchema = object({
18706
+ node: FrameLazyCountersSchema,
18707
+ cameras: array(FrameLazyCountersSchema.extend({ deviceId: number() }))
18708
+ });
18620
18709
  var RunnerLocalMetricsSchema = object({
18621
18710
  nodeId: string(),
18622
18711
  activeCameras: number(),
18623
18712
  throttledCameras: number(),
18624
18713
  avgInferenceTimeMs: number(),
18625
- queueDepth: number()
18714
+ queueDepth: number(),
18715
+ frameLazy: FrameLazyMetricsSchema.optional()
18626
18716
  });
18627
18717
  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({
18628
18718
  handle: FrameHandleSchema,
@@ -19922,6 +20012,9 @@ method(_void(), ProviderInfoSchema), method(object({ config: record(string(), un
19922
20012
  location: StorageLocationSchema,
19923
20013
  relativePath: string()
19924
20014
  }), _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" });
20015
+ /** Profile-exported FormBuilder schema. Shape is ConfigUISchema at the UI. */
20016
+ var ProfileSettingsSchemaBridge = unknown().nullable();
20017
+ var ProfileSettingsBagSchema = record(string(), unknown());
19925
20018
  /**
19926
20019
  * A live terminal session hosted by the provider addon. Output and input do
19927
20020
  * NOT flow through the capability — they use the addon data plane
@@ -19951,7 +20044,14 @@ var TerminalSessionInfoSchema = object({
19951
20044
  var TerminalProfileInfoSchema = object({
19952
20045
  profileId: string(),
19953
20046
  label: string(),
19954
- description: string().optional()
20047
+ description: string().optional(),
20048
+ /** Spawn defaults the instance form copies on create. */
20049
+ executable: string().optional(),
20050
+ args: array(string()).readonly().optional(),
20051
+ cwd: string().optional(),
20052
+ environment: array(string()).readonly().optional(),
20053
+ /** ConfigUISchema for instance knobs, or null when the profile has none. */
20054
+ settingsSchema: ProfileSettingsSchemaBridge.optional()
19955
20055
  });
19956
20056
  /**
19957
20057
  * A durable operator-created Terminal instance. Profiles are templates; only
@@ -19964,7 +20064,12 @@ var TerminalInstanceInfoSchema = object({
19964
20064
  profileId: string(),
19965
20065
  profileLabel: string(),
19966
20066
  name: string(),
19967
- enabled: boolean()
20067
+ enabled: boolean(),
20068
+ executable: string(),
20069
+ args: array(string()).readonly(),
20070
+ cwd: string(),
20071
+ environment: array(string()).readonly(),
20072
+ profileSettings: ProfileSettingsBagSchema
19968
20073
  });
19969
20074
  var TerminalLegacyCameraSchema = object({
19970
20075
  stableId: string(),
@@ -19994,7 +20099,23 @@ var TerminalOutputBatchSchema = object({
19994
20099
  method(_void(), array(TerminalProfileInfoSchema).readonly(), { auth: "admin" }), method(_void(), array(TerminalInstanceInfoSchema).readonly(), { auth: "admin" }), method(object({
19995
20100
  targetNodeId: string().min(1),
19996
20101
  profileId: string().min(1),
19997
- name: string().trim().min(1).max(160).optional()
20102
+ name: string().trim().min(1).max(160).optional(),
20103
+ executable: string().max(1024).optional(),
20104
+ args: array(string().max(2048)).max(64).optional(),
20105
+ cwd: string().max(1024).optional(),
20106
+ environment: array(string().max(4096)).max(64).optional(),
20107
+ profileSettings: ProfileSettingsBagSchema.optional()
20108
+ }), TerminalInstanceInfoSchema, {
20109
+ kind: "mutation",
20110
+ auth: "admin"
20111
+ }), method(object({
20112
+ instanceId: string().min(1),
20113
+ name: string().trim().min(1).max(160).optional(),
20114
+ executable: string().max(1024).optional(),
20115
+ args: array(string().max(2048)).max(64).optional(),
20116
+ cwd: string().max(1024).optional(),
20117
+ environment: array(string().max(4096)).max(64).optional(),
20118
+ profileSettings: ProfileSettingsBagSchema.optional()
19998
20119
  }), TerminalInstanceInfoSchema, {
19999
20120
  kind: "mutation",
20000
20121
  auth: "admin"
@@ -20016,7 +20137,11 @@ method(_void(), array(TerminalProfileInfoSchema).readonly(), { auth: "admin" }),
20016
20137
  }), method(_void(), array(TerminalSessionInfoSchema).readonly(), { auth: "admin" }), method(object({
20017
20138
  profileId: string(),
20018
20139
  cols: number().int().positive(),
20019
- rows: number().int().positive()
20140
+ rows: number().int().positive(),
20141
+ executable: string().max(1024).optional(),
20142
+ args: array(string().max(2048)).max(64).optional(),
20143
+ cwd: string().max(1024).optional(),
20144
+ environment: array(string().max(4096)).max(64).optional()
20020
20145
  }), TerminalSessionInfoSchema, {
20021
20146
  kind: "mutation",
20022
20147
  auth: "admin"
@@ -23891,10 +24016,10 @@ var lawnMowerControlCapability = {
23891
24016
  *
23892
24017
  * • The SDK (mobile / web client) consumes `getConnectionEndpoints()`
23893
24018
  * to receive an ordered list of candidate base URLs it should race
23894
- * on connect — LAN IPv4 first (lowest latency when on same network),
23895
- * then public hostname (if a tunnel is up), then IPv6. The SDK can
23896
- * race them with short timeouts and stick with the winner for the
23897
- * session.
24019
+ * on connect — LAN IPv4 and stable LAN IPv6 first (lowest latency
24020
+ * when on the same network), then public hostname (if a tunnel is
24021
+ * up). The SDK can race them with short timeouts and stick with the
24022
+ * winner for the session.
23898
24023
  *
23899
24024
  * Why hub-only: agents are not directly addressable by the operator's
23900
24025
  * clients — they reverse-connect to the hub. Exposing their interfaces
@@ -24049,6 +24174,17 @@ var NotificationEndpointSchema = object({
24049
24174
  /** What the ranking currently resolves to (null when nothing is reachable). */
24050
24175
  resolved: string().nullable()
24051
24176
  });
24177
+ /**
24178
+ * The URLs the SDK / viewer should race for API access. `baseUrls` empty =
24179
+ * AUTO (every LAN IPv4 + the public tunnel). `resolved` is what that choice
24180
+ * currently expands to, so the UI can show the effective set either way.
24181
+ */
24182
+ var ViewerEndpointsSchema = object({
24183
+ /** The operator's explicit race set, or empty for AUTO. */
24184
+ baseUrls: array(string()).readonly(),
24185
+ /** What the ranking currently resolves to (may be empty if nothing is up). */
24186
+ resolved: array(string()).readonly()
24187
+ });
24052
24188
  var AllowedAddressesSchema = object({
24053
24189
  /**
24054
24190
  * Allowlist of interface addresses operators have explicitly opted
@@ -24057,6 +24193,20 @@ var AllowedAddressesSchema = object({
24057
24193
  * Network Addresses admin page and persisted by the addon.
24058
24194
  */
24059
24195
  addresses: array(string()).readonly() });
24196
+ var TlsStatusSchema = object({
24197
+ mode: _enum([
24198
+ "generated",
24199
+ "uploaded",
24200
+ "disabled"
24201
+ ]),
24202
+ leafFingerprintSha256: string().nullable(),
24203
+ caFingerprintSha256: string().nullable(),
24204
+ validTo: string().nullable(),
24205
+ sans: array(string()),
24206
+ caCertPem: string().nullable(),
24207
+ reissueError: string().nullable(),
24208
+ restartRequired: boolean()
24209
+ });
24060
24210
  method(_void(), ListResultSchema), method(_void(), PreferredSchema), method(object({
24061
24211
  /**
24062
24212
  * LEGACY HINT — do not send from new code. Kept optional so clients
@@ -24066,17 +24216,31 @@ method(_void(), ListResultSchema), method(_void(), PreferredSchema), method(obje
24066
24216
  */
24067
24217
  port: number().int().min(1).max(65535).optional(),
24068
24218
  /** Include `http(s)://127.0.0.1:<port>` as the lowest-priority
24069
- * candidate. Default `true`. */
24219
+ * candidate. Default `false` — loopback is not a client route. */
24070
24220
  includeLoopback: boolean().optional(),
24071
- /** Skip IPv6 entries. Some legacy clients can't parse them.
24072
- * Default `false`. */
24221
+ /** Skip IPv6 entries. Default `false` — the palette includes stable
24222
+ * LAN IPv6 (ICE/WebRTC uses dual-stack regardless). Pass `true` to
24223
+ * hide them. The viewer HTTP/WS race is `getViewerEndpoints`. */
24073
24224
  ipv4Only: boolean().optional(),
24074
24225
  /** Scheme to emit for LAN/loopback URLs. Default `'http'`.
24075
24226
  * Pass `'https'` when the caller is itself loaded over HTTPS
24076
24227
  * to avoid mixed-content blocks in the browser. The public
24077
24228
  * tunnel always emits `https://` regardless. */
24078
24229
  scheme: _enum(["http", "https"]).optional()
24079
- }), 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" });
24230
+ }), 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, {
24231
+ kind: "mutation",
24232
+ auth: "admin"
24233
+ }), method(object({
24234
+ certPem: string().min(1),
24235
+ keyPem: string().min(1),
24236
+ caPem: string().optional()
24237
+ }), TlsStatusSchema, {
24238
+ kind: "mutation",
24239
+ auth: "admin"
24240
+ }), method(_void(), object({ pem: string() })), method(_void(), TlsStatusSchema, {
24241
+ kind: "mutation",
24242
+ auth: "admin"
24243
+ });
24080
24244
  var LockControlStatusSchema = object({
24081
24245
  /** Lifecycle state of the lock. `jammed` means the motor reported
24082
24246
  * failure to reach the target — operator intervention required. */
@@ -32103,6 +32267,12 @@ Object.freeze({
32103
32267
  addonId: null,
32104
32268
  access: "create"
32105
32269
  },
32270
+ "localNetwork.downloadCa": {
32271
+ capName: "local-network",
32272
+ capScope: "system",
32273
+ addonId: null,
32274
+ access: "view"
32275
+ },
32106
32276
  "localNetwork.getAllowedAddresses": {
32107
32277
  capName: "local-network",
32108
32278
  capScope: "system",
@@ -32127,18 +32297,42 @@ Object.freeze({
32127
32297
  addonId: null,
32128
32298
  access: "view"
32129
32299
  },
32300
+ "localNetwork.getTlsStatus": {
32301
+ capName: "local-network",
32302
+ capScope: "system",
32303
+ addonId: null,
32304
+ access: "view"
32305
+ },
32306
+ "localNetwork.getViewerEndpoints": {
32307
+ capName: "local-network",
32308
+ capScope: "system",
32309
+ addonId: null,
32310
+ access: "view"
32311
+ },
32130
32312
  "localNetwork.list": {
32131
32313
  capName: "local-network",
32132
32314
  capScope: "system",
32133
32315
  addonId: null,
32134
32316
  access: "view"
32135
32317
  },
32318
+ "localNetwork.regenerateCertificate": {
32319
+ capName: "local-network",
32320
+ capScope: "system",
32321
+ addonId: null,
32322
+ access: "create"
32323
+ },
32136
32324
  "localNetwork.resetAllowlistToBestMatch": {
32137
32325
  capName: "local-network",
32138
32326
  capScope: "system",
32139
32327
  addonId: null,
32140
32328
  access: "delete"
32141
32329
  },
32330
+ "localNetwork.revertToGeneratedCertificate": {
32331
+ capName: "local-network",
32332
+ capScope: "system",
32333
+ addonId: null,
32334
+ access: "create"
32335
+ },
32142
32336
  "localNetwork.setAllowedAddresses": {
32143
32337
  capName: "local-network",
32144
32338
  capScope: "system",
@@ -32151,6 +32345,18 @@ Object.freeze({
32151
32345
  addonId: null,
32152
32346
  access: "create"
32153
32347
  },
32348
+ "localNetwork.setViewerEndpoints": {
32349
+ capName: "local-network",
32350
+ capScope: "system",
32351
+ addonId: null,
32352
+ access: "create"
32353
+ },
32354
+ "localNetwork.uploadCertificate": {
32355
+ capName: "local-network",
32356
+ capScope: "system",
32357
+ addonId: null,
32358
+ access: "create"
32359
+ },
32154
32360
  "lockControl.lock": {
32155
32361
  capName: "lock-control",
32156
32362
  capScope: "device",
@@ -35031,6 +35237,12 @@ Object.freeze({
35031
35237
  addonId: null,
35032
35238
  access: "create"
35033
35239
  },
35240
+ "terminalSession.updateInstance": {
35241
+ capName: "terminal-session",
35242
+ capScope: "system",
35243
+ addonId: null,
35244
+ access: "create"
35245
+ },
35034
35246
  "terminalSession.writeInput": {
35035
35247
  capName: "terminal-session",
35036
35248
  capScope: "system",
@@ -37518,6 +37730,35 @@ Object.freeze(Object.fromEntries([{
37518
37730
  }]
37519
37731
  }].map((s) => [s.stepId, s.defaultModelId])));
37520
37732
  string().min(1);
37733
+ var CLUSTER_STEP_SETTING_FIELDS = [{
37734
+ stepId: "face-embedding",
37735
+ key: "minLandmarkFaceSize",
37736
+ label: "Min face size for recognition (detection px)",
37737
+ 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.",
37738
+ type: "slider",
37739
+ min: 0,
37740
+ max: 64,
37741
+ step: 2,
37742
+ default: 24
37743
+ }];
37744
+ function clusterStepSettingKey(stepId, fieldKey) {
37745
+ return `clusterStepSetting:${stepId}:${fieldKey}`;
37746
+ }
37747
+ var ClusterSettingNumberSchema = number().finite();
37748
+ function readClusterStepSettings(config) {
37749
+ const out = {};
37750
+ for (const field of CLUSTER_STEP_SETTING_FIELDS) {
37751
+ const parsed = ClusterSettingNumberSchema.safeParse(config[clusterStepSettingKey(field.stepId, field.key)]);
37752
+ const value = parsed.success ? parsed.data : field.default;
37753
+ const existing = out[field.stepId] ?? {};
37754
+ out[field.stepId] = {
37755
+ ...existing,
37756
+ [field.key]: value
37757
+ };
37758
+ }
37759
+ return out;
37760
+ }
37761
+ readClusterStepSettings({});
37521
37762
  object({
37522
37763
  /**
37523
37764
  * 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-homematic",
3
- "version": "1.2.26",
3
+ "version": "1.2.28",
4
4
  "description": "Homematic / HomematicIP (CCU3 / RaspberryMatic) device-provider addon for CamStack — wraps the nodehomematic library",
5
5
  "keywords": [
6
6
  "camstack",