@camstack/addon-mqtt-broker 1.2.25 → 1.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.
@@ -44,7 +44,7 @@ let crypto$10 = __toESM(crypto$1, 1);
44
44
  crypto$1 = __toESM(crypto$1);
45
45
  let node_fs = require("node:fs");
46
46
  let node_path = require("node:path");
47
- //#region ../types/dist/event-category-XfKNtfCc.mjs
47
+ //#region ../types/dist/event-category-CIa_iT6b.mjs
48
48
  var EventCategory = /* @__PURE__ */ function(EventCategory) {
49
49
  EventCategory["SystemBoot"] = "system.boot";
50
50
  EventCategory["SystemAddonsReady"] = "system.addons-ready";
@@ -60,6 +60,15 @@ var EventCategory = /* @__PURE__ */ function(EventCategory) {
60
60
  */
61
61
  EventCategory["SystemRestartCompleted"] = "system.restart-completed";
62
62
  /**
63
+ * The hub reissued its own TLS certificate at boot (`ensureTlsCert`).
64
+ * Emitted only when the material on disk actually changed, so an
65
+ * operator who trusted the old certificate by hand is told rather than
66
+ * discovering it as a browser error. Payload `TlsCertChangedPayload`.
67
+ *
68
+ * Rule: docs/decisions/adr-0227-*.md
69
+ */
70
+ EventCategory["SystemTlsCertChanged"] = "system.tls-cert-changed";
71
+ /**
63
72
  * A newer addon or server-root package version was found by the
64
73
  * authoritative registry check. Emitted once when any observed
65
74
  * `latestVersion` changes (or a package/node first appears behind);
@@ -8040,6 +8049,15 @@ var LabelDefinitionSchema = object({
8040
8049
  description: string().optional(),
8041
8050
  icon: string().optional()
8042
8051
  });
8052
+ var ClassMapDefinitionSchema = object({
8053
+ mapping: record(string(), _enum([
8054
+ "person",
8055
+ "vehicle",
8056
+ "animal",
8057
+ "package"
8058
+ ])),
8059
+ preserveOriginal: boolean()
8060
+ });
8043
8061
  var MODEL_FORMATS = [
8044
8062
  "onnx",
8045
8063
  "coreml",
@@ -8218,7 +8236,13 @@ var ModelCatalogEntrySchema = object({
8218
8236
  * `id` stays the source of truth for resolution/download/persistence; grouping
8219
8237
  * is a presentation overlay resolved back to an `id`.
8220
8238
  */
8221
- group: ModelVariantGroupSchema.optional()
8239
+ group: ModelVariantGroupSchema.optional(),
8240
+ /**
8241
+ * Per-MODEL class map override. Absent ⇒ the step's `StepDefinition.classMap`
8242
+ * applies (Frigate / COCO public catalog). Set on a custom model whose raw
8243
+ * labels already ARE the CamStack macros (Scrypted identity map).
8244
+ */
8245
+ classMap: ClassMapDefinitionSchema.optional()
8222
8246
  });
8223
8247
  var ConvertTargetSchema = discriminatedUnion("format", [object({
8224
8248
  format: literal("openvino"),
@@ -8247,7 +8271,8 @@ var ModelConvertMetadataSchema = object({
8247
8271
  "ocr",
8248
8272
  "segmentation"
8249
8273
  ]),
8250
- faceAlignment: boolean().optional()
8274
+ faceAlignment: boolean().optional(),
8275
+ classMap: ClassMapDefinitionSchema.optional()
8251
8276
  });
8252
8277
  var ConvertResultSchema = object({
8253
8278
  entry: ModelCatalogEntrySchema,
@@ -17306,6 +17331,33 @@ var NativeCropRefSchema = object({
17306
17331
  h: number()
17307
17332
  })
17308
17333
  });
17334
+ object({
17335
+ crop: object({
17336
+ left: number(),
17337
+ top: number(),
17338
+ width: number().positive(),
17339
+ height: number().positive()
17340
+ }).optional(),
17341
+ content: object({
17342
+ width: number().int().positive(),
17343
+ height: number().int().positive()
17344
+ }),
17345
+ fit: _enum(["stretch", "contain"]),
17346
+ format: _enum([
17347
+ "rgb",
17348
+ "gray",
17349
+ "jpeg"
17350
+ ])
17351
+ });
17352
+ var FrameRefSchema = object({
17353
+ registryId: string().min(1),
17354
+ id: string().min(1),
17355
+ width: number().int().positive(),
17356
+ height: number().int().positive(),
17357
+ format: _enum(["rgb", "gray"]),
17358
+ timestamp: number(),
17359
+ capturedAt: number().optional()
17360
+ });
17309
17361
  var ModelFormatSchema$1 = _enum([
17310
17362
  "onnx",
17311
17363
  "coreml",
@@ -17550,6 +17602,12 @@ method(_void(), array(PipelineEngineChoiceSchema)), method(_void(), PipelineEngi
17550
17602
  steps: array(PipelineStepInputSchema).min(1),
17551
17603
  frame: FrameInputSchema.optional(),
17552
17604
  /**
17605
+ * Process-local lazy frame. Valid only when caller and provider resolve
17606
+ * in the same execution-group process; split/cross-node callers use
17607
+ * `frame`/`image` inline compatibility instead.
17608
+ */
17609
+ frameRef: FrameRefSchema.optional(),
17610
+ /**
17553
17611
  * CB5 shm passthrough — a `FrameHandle` naming the same ring slot
17554
17612
  * the decoded pixels live in. One more member of the one-of
17555
17613
  * frame/frameHandle/image/imageBase64/referenceImage group.
@@ -17805,7 +17863,10 @@ var NativeCropResultSchema = object({
17805
17863
  * Which source served this crop, so a quality-sensitive consumer (the native
17806
17864
  * `keyFrame`) can reject a degraded fallback:
17807
17865
  * - `native` — cut from the decode worker's retained NATIVE surface (the
17808
- * quality path).
17866
+ * quality path). A subject-tile serve is also native-resolution and stays
17867
+ * `native` here: the public enum cannot name `tile` without a breaking cap
17868
+ * change. Runner telemetry distinguishes lease vs tile via `source` on the
17869
+ * internal crop result (`nativeHits` vs `tileHits`).
17809
17870
  * - `ram-fullframe` — the native surface MISSED but the request was full-frame,
17810
17871
  * so the ≤640 RAM `RetainedFrameStore` served it (honest lower-res; legit for
17811
17872
  * the blank-frame guard / 640-snapshot resolve, NOT for the clean keyFrame).
@@ -18296,12 +18357,41 @@ var RunnerLocalLoadSchema = object({
18296
18357
  * legacy `OrchestratorMetricsSchema` shape so existing dashboards keep
18297
18358
  * working unchanged when they switch to reading from the runner cap.
18298
18359
  */
18360
+ var FrameLazyCountersSchema = object({
18361
+ framesDecoded: number(),
18362
+ framesAdmitted: number(),
18363
+ framesDroppedPixelFree: number(),
18364
+ viewsMaterialized: number(),
18365
+ viewsSkipped: number(),
18366
+ workerToRunnerBytes: number(),
18367
+ runnerToPoolRawBytes: number(),
18368
+ runnerToPoolJpegBytes: number(),
18369
+ onDemandFullFrameRequests: number(),
18370
+ onDemandCropRequests: number(),
18371
+ nativeHits: number(),
18372
+ nativeMisses: number(),
18373
+ tileHits: number(),
18374
+ tileMisses: number(),
18375
+ fallbackHits: number(),
18376
+ fallbackMisses: number(),
18377
+ retainedWritesAvoided: number(),
18378
+ residentRefs: number(),
18379
+ residentBytes: number(),
18380
+ releases: number(),
18381
+ evictions: number(),
18382
+ staleMisses: number()
18383
+ });
18384
+ var FrameLazyMetricsSchema = object({
18385
+ node: FrameLazyCountersSchema,
18386
+ cameras: array(FrameLazyCountersSchema.extend({ deviceId: number() }))
18387
+ });
18299
18388
  var RunnerLocalMetricsSchema = object({
18300
18389
  nodeId: string(),
18301
18390
  activeCameras: number(),
18302
18391
  throttledCameras: number(),
18303
18392
  avgInferenceTimeMs: number(),
18304
- queueDepth: number()
18393
+ queueDepth: number(),
18394
+ frameLazy: FrameLazyMetricsSchema.optional()
18305
18395
  });
18306
18396
  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({
18307
18397
  handle: FrameHandleSchema,
@@ -19601,6 +19691,9 @@ method(_void(), ProviderInfoSchema), method(object({ config: record(string(), un
19601
19691
  location: StorageLocationSchema,
19602
19692
  relativePath: string()
19603
19693
  }), _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" });
19694
+ /** Profile-exported FormBuilder schema. Shape is ConfigUISchema at the UI. */
19695
+ var ProfileSettingsSchemaBridge = unknown().nullable();
19696
+ var ProfileSettingsBagSchema = record(string(), unknown());
19604
19697
  /**
19605
19698
  * A live terminal session hosted by the provider addon. Output and input do
19606
19699
  * NOT flow through the capability — they use the addon data plane
@@ -19630,7 +19723,14 @@ var TerminalSessionInfoSchema = object({
19630
19723
  var TerminalProfileInfoSchema = object({
19631
19724
  profileId: string(),
19632
19725
  label: string(),
19633
- description: string().optional()
19726
+ description: string().optional(),
19727
+ /** Spawn defaults the instance form copies on create. */
19728
+ executable: string().optional(),
19729
+ args: array(string()).readonly().optional(),
19730
+ cwd: string().optional(),
19731
+ environment: array(string()).readonly().optional(),
19732
+ /** ConfigUISchema for instance knobs, or null when the profile has none. */
19733
+ settingsSchema: ProfileSettingsSchemaBridge.optional()
19634
19734
  });
19635
19735
  /**
19636
19736
  * A durable operator-created Terminal instance. Profiles are templates; only
@@ -19643,7 +19743,12 @@ var TerminalInstanceInfoSchema = object({
19643
19743
  profileId: string(),
19644
19744
  profileLabel: string(),
19645
19745
  name: string(),
19646
- enabled: boolean()
19746
+ enabled: boolean(),
19747
+ executable: string(),
19748
+ args: array(string()).readonly(),
19749
+ cwd: string(),
19750
+ environment: array(string()).readonly(),
19751
+ profileSettings: ProfileSettingsBagSchema
19647
19752
  });
19648
19753
  var TerminalLegacyCameraSchema = object({
19649
19754
  stableId: string(),
@@ -19673,7 +19778,23 @@ var TerminalOutputBatchSchema = object({
19673
19778
  method(_void(), array(TerminalProfileInfoSchema).readonly(), { auth: "admin" }), method(_void(), array(TerminalInstanceInfoSchema).readonly(), { auth: "admin" }), method(object({
19674
19779
  targetNodeId: string().min(1),
19675
19780
  profileId: string().min(1),
19676
- name: string().trim().min(1).max(160).optional()
19781
+ name: string().trim().min(1).max(160).optional(),
19782
+ executable: string().max(1024).optional(),
19783
+ args: array(string().max(2048)).max(64).optional(),
19784
+ cwd: string().max(1024).optional(),
19785
+ environment: array(string().max(4096)).max(64).optional(),
19786
+ profileSettings: ProfileSettingsBagSchema.optional()
19787
+ }), TerminalInstanceInfoSchema, {
19788
+ kind: "mutation",
19789
+ auth: "admin"
19790
+ }), method(object({
19791
+ instanceId: string().min(1),
19792
+ name: string().trim().min(1).max(160).optional(),
19793
+ executable: string().max(1024).optional(),
19794
+ args: array(string().max(2048)).max(64).optional(),
19795
+ cwd: string().max(1024).optional(),
19796
+ environment: array(string().max(4096)).max(64).optional(),
19797
+ profileSettings: ProfileSettingsBagSchema.optional()
19677
19798
  }), TerminalInstanceInfoSchema, {
19678
19799
  kind: "mutation",
19679
19800
  auth: "admin"
@@ -19695,7 +19816,11 @@ method(_void(), array(TerminalProfileInfoSchema).readonly(), { auth: "admin" }),
19695
19816
  }), method(_void(), array(TerminalSessionInfoSchema).readonly(), { auth: "admin" }), method(object({
19696
19817
  profileId: string(),
19697
19818
  cols: number().int().positive(),
19698
- rows: number().int().positive()
19819
+ rows: number().int().positive(),
19820
+ executable: string().max(1024).optional(),
19821
+ args: array(string().max(2048)).max(64).optional(),
19822
+ cwd: string().max(1024).optional(),
19823
+ environment: array(string().max(4096)).max(64).optional()
19699
19824
  }), TerminalSessionInfoSchema, {
19700
19825
  kind: "mutation",
19701
19826
  auth: "admin"
@@ -22477,10 +22602,10 @@ DeviceType.LawnMower, method(object({ deviceId: number().int().nonnegative() }),
22477
22602
  *
22478
22603
  * • The SDK (mobile / web client) consumes `getConnectionEndpoints()`
22479
22604
  * to receive an ordered list of candidate base URLs it should race
22480
- * on connect — LAN IPv4 first (lowest latency when on same network),
22481
- * then public hostname (if a tunnel is up), then IPv6. The SDK can
22482
- * race them with short timeouts and stick with the winner for the
22483
- * session.
22605
+ * on connect — LAN IPv4 and stable LAN IPv6 first (lowest latency
22606
+ * when on the same network), then public hostname (if a tunnel is
22607
+ * up). The SDK can race them with short timeouts and stick with the
22608
+ * winner for the session.
22484
22609
  *
22485
22610
  * Why hub-only: agents are not directly addressable by the operator's
22486
22611
  * clients — they reverse-connect to the hub. Exposing their interfaces
@@ -22635,6 +22760,17 @@ var NotificationEndpointSchema = object({
22635
22760
  /** What the ranking currently resolves to (null when nothing is reachable). */
22636
22761
  resolved: string().nullable()
22637
22762
  });
22763
+ /**
22764
+ * The URLs the SDK / viewer should race for API access. `baseUrls` empty =
22765
+ * AUTO (every LAN IPv4 + the public tunnel). `resolved` is what that choice
22766
+ * currently expands to, so the UI can show the effective set either way.
22767
+ */
22768
+ var ViewerEndpointsSchema = object({
22769
+ /** The operator's explicit race set, or empty for AUTO. */
22770
+ baseUrls: array(string()).readonly(),
22771
+ /** What the ranking currently resolves to (may be empty if nothing is up). */
22772
+ resolved: array(string()).readonly()
22773
+ });
22638
22774
  var AllowedAddressesSchema = object({
22639
22775
  /**
22640
22776
  * Allowlist of interface addresses operators have explicitly opted
@@ -22643,6 +22779,20 @@ var AllowedAddressesSchema = object({
22643
22779
  * Network Addresses admin page and persisted by the addon.
22644
22780
  */
22645
22781
  addresses: array(string()).readonly() });
22782
+ var TlsStatusSchema = object({
22783
+ mode: _enum([
22784
+ "generated",
22785
+ "uploaded",
22786
+ "disabled"
22787
+ ]),
22788
+ leafFingerprintSha256: string().nullable(),
22789
+ caFingerprintSha256: string().nullable(),
22790
+ validTo: string().nullable(),
22791
+ sans: array(string()),
22792
+ caCertPem: string().nullable(),
22793
+ reissueError: string().nullable(),
22794
+ restartRequired: boolean()
22795
+ });
22646
22796
  method(_void(), ListResultSchema), method(_void(), PreferredSchema), method(object({
22647
22797
  /**
22648
22798
  * LEGACY HINT — do not send from new code. Kept optional so clients
@@ -22652,17 +22802,31 @@ method(_void(), ListResultSchema), method(_void(), PreferredSchema), method(obje
22652
22802
  */
22653
22803
  port: number().int().min(1).max(65535).optional(),
22654
22804
  /** Include `http(s)://127.0.0.1:<port>` as the lowest-priority
22655
- * candidate. Default `true`. */
22805
+ * candidate. Default `false` — loopback is not a client route. */
22656
22806
  includeLoopback: boolean().optional(),
22657
- /** Skip IPv6 entries. Some legacy clients can't parse them.
22658
- * Default `false`. */
22807
+ /** Skip IPv6 entries. Default `false` the palette includes stable
22808
+ * LAN IPv6 (ICE/WebRTC uses dual-stack regardless). Pass `true` to
22809
+ * hide them. The viewer HTTP/WS race is `getViewerEndpoints`. */
22659
22810
  ipv4Only: boolean().optional(),
22660
22811
  /** Scheme to emit for LAN/loopback URLs. Default `'http'`.
22661
22812
  * Pass `'https'` when the caller is itself loaded over HTTPS
22662
22813
  * to avoid mixed-content blocks in the browser. The public
22663
22814
  * tunnel always emits `https://` regardless. */
22664
22815
  scheme: _enum(["http", "https"]).optional()
22665
- }), 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" });
22816
+ }), 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, {
22817
+ kind: "mutation",
22818
+ auth: "admin"
22819
+ }), method(object({
22820
+ certPem: string().min(1),
22821
+ keyPem: string().min(1),
22822
+ caPem: string().optional()
22823
+ }), TlsStatusSchema, {
22824
+ kind: "mutation",
22825
+ auth: "admin"
22826
+ }), method(_void(), object({ pem: string() })), method(_void(), TlsStatusSchema, {
22827
+ kind: "mutation",
22828
+ auth: "admin"
22829
+ });
22666
22830
  object({
22667
22831
  /** Lifecycle state of the lock. `jammed` means the motor reported
22668
22832
  * failure to reach the target — operator intervention required. */
@@ -28425,6 +28589,12 @@ Object.freeze({
28425
28589
  addonId: null,
28426
28590
  access: "create"
28427
28591
  },
28592
+ "localNetwork.downloadCa": {
28593
+ capName: "local-network",
28594
+ capScope: "system",
28595
+ addonId: null,
28596
+ access: "view"
28597
+ },
28428
28598
  "localNetwork.getAllowedAddresses": {
28429
28599
  capName: "local-network",
28430
28600
  capScope: "system",
@@ -28449,18 +28619,42 @@ Object.freeze({
28449
28619
  addonId: null,
28450
28620
  access: "view"
28451
28621
  },
28622
+ "localNetwork.getTlsStatus": {
28623
+ capName: "local-network",
28624
+ capScope: "system",
28625
+ addonId: null,
28626
+ access: "view"
28627
+ },
28628
+ "localNetwork.getViewerEndpoints": {
28629
+ capName: "local-network",
28630
+ capScope: "system",
28631
+ addonId: null,
28632
+ access: "view"
28633
+ },
28452
28634
  "localNetwork.list": {
28453
28635
  capName: "local-network",
28454
28636
  capScope: "system",
28455
28637
  addonId: null,
28456
28638
  access: "view"
28457
28639
  },
28640
+ "localNetwork.regenerateCertificate": {
28641
+ capName: "local-network",
28642
+ capScope: "system",
28643
+ addonId: null,
28644
+ access: "create"
28645
+ },
28458
28646
  "localNetwork.resetAllowlistToBestMatch": {
28459
28647
  capName: "local-network",
28460
28648
  capScope: "system",
28461
28649
  addonId: null,
28462
28650
  access: "delete"
28463
28651
  },
28652
+ "localNetwork.revertToGeneratedCertificate": {
28653
+ capName: "local-network",
28654
+ capScope: "system",
28655
+ addonId: null,
28656
+ access: "create"
28657
+ },
28464
28658
  "localNetwork.setAllowedAddresses": {
28465
28659
  capName: "local-network",
28466
28660
  capScope: "system",
@@ -28473,6 +28667,18 @@ Object.freeze({
28473
28667
  addonId: null,
28474
28668
  access: "create"
28475
28669
  },
28670
+ "localNetwork.setViewerEndpoints": {
28671
+ capName: "local-network",
28672
+ capScope: "system",
28673
+ addonId: null,
28674
+ access: "create"
28675
+ },
28676
+ "localNetwork.uploadCertificate": {
28677
+ capName: "local-network",
28678
+ capScope: "system",
28679
+ addonId: null,
28680
+ access: "create"
28681
+ },
28476
28682
  "lockControl.lock": {
28477
28683
  capName: "lock-control",
28478
28684
  capScope: "device",
@@ -31353,6 +31559,12 @@ Object.freeze({
31353
31559
  addonId: null,
31354
31560
  access: "create"
31355
31561
  },
31562
+ "terminalSession.updateInstance": {
31563
+ capName: "terminal-session",
31564
+ capScope: "system",
31565
+ addonId: null,
31566
+ access: "create"
31567
+ },
31356
31568
  "terminalSession.writeInput": {
31357
31569
  capName: "terminal-session",
31358
31570
  capScope: "system",
@@ -33840,6 +34052,35 @@ Object.freeze(Object.fromEntries([{
33840
34052
  }]
33841
34053
  }].map((s) => [s.stepId, s.defaultModelId])));
33842
34054
  string().min(1);
34055
+ var CLUSTER_STEP_SETTING_FIELDS = [{
34056
+ stepId: "face-embedding",
34057
+ key: "minLandmarkFaceSize",
34058
+ label: "Min face size for recognition (detection px)",
34059
+ 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.",
34060
+ type: "slider",
34061
+ min: 0,
34062
+ max: 64,
34063
+ step: 2,
34064
+ default: 24
34065
+ }];
34066
+ function clusterStepSettingKey(stepId, fieldKey) {
34067
+ return `clusterStepSetting:${stepId}:${fieldKey}`;
34068
+ }
34069
+ var ClusterSettingNumberSchema = number().finite();
34070
+ function readClusterStepSettings(config) {
34071
+ const out = {};
34072
+ for (const field of CLUSTER_STEP_SETTING_FIELDS) {
34073
+ const parsed = ClusterSettingNumberSchema.safeParse(config[clusterStepSettingKey(field.stepId, field.key)]);
34074
+ const value = parsed.success ? parsed.data : field.default;
34075
+ const existing = out[field.stepId] ?? {};
34076
+ out[field.stepId] = {
34077
+ ...existing,
34078
+ [field.key]: value
34079
+ };
34080
+ }
34081
+ return out;
34082
+ }
34083
+ readClusterStepSettings({});
33843
34084
  object({
33844
34085
  /**
33845
34086
  * Fraction of the box's own size added on EACH side before cutting.
@@ -39,7 +39,7 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
39
39
  var __toCommonJS = (mod) => __hasOwnProp.call(mod, "module.exports") ? mod["module.exports"] : __copyProps(__defProp({}, "__esModule", { value: true }), mod);
40
40
  var __require = /* @__PURE__ */ createRequire(import.meta.url);
41
41
  //#endregion
42
- //#region ../types/dist/event-category-XfKNtfCc.mjs
42
+ //#region ../types/dist/event-category-CIa_iT6b.mjs
43
43
  var EventCategory = /* @__PURE__ */ function(EventCategory) {
44
44
  EventCategory["SystemBoot"] = "system.boot";
45
45
  EventCategory["SystemAddonsReady"] = "system.addons-ready";
@@ -55,6 +55,15 @@ var EventCategory = /* @__PURE__ */ function(EventCategory) {
55
55
  */
56
56
  EventCategory["SystemRestartCompleted"] = "system.restart-completed";
57
57
  /**
58
+ * The hub reissued its own TLS certificate at boot (`ensureTlsCert`).
59
+ * Emitted only when the material on disk actually changed, so an
60
+ * operator who trusted the old certificate by hand is told rather than
61
+ * discovering it as a browser error. Payload `TlsCertChangedPayload`.
62
+ *
63
+ * Rule: docs/decisions/adr-0227-*.md
64
+ */
65
+ EventCategory["SystemTlsCertChanged"] = "system.tls-cert-changed";
66
+ /**
58
67
  * A newer addon or server-root package version was found by the
59
68
  * authoritative registry check. Emitted once when any observed
60
69
  * `latestVersion` changes (or a package/node first appears behind);
@@ -8035,6 +8044,15 @@ var LabelDefinitionSchema = object({
8035
8044
  description: string().optional(),
8036
8045
  icon: string().optional()
8037
8046
  });
8047
+ var ClassMapDefinitionSchema = object({
8048
+ mapping: record(string(), _enum([
8049
+ "person",
8050
+ "vehicle",
8051
+ "animal",
8052
+ "package"
8053
+ ])),
8054
+ preserveOriginal: boolean()
8055
+ });
8038
8056
  var MODEL_FORMATS = [
8039
8057
  "onnx",
8040
8058
  "coreml",
@@ -8213,7 +8231,13 @@ var ModelCatalogEntrySchema = object({
8213
8231
  * `id` stays the source of truth for resolution/download/persistence; grouping
8214
8232
  * is a presentation overlay resolved back to an `id`.
8215
8233
  */
8216
- group: ModelVariantGroupSchema.optional()
8234
+ group: ModelVariantGroupSchema.optional(),
8235
+ /**
8236
+ * Per-MODEL class map override. Absent ⇒ the step's `StepDefinition.classMap`
8237
+ * applies (Frigate / COCO public catalog). Set on a custom model whose raw
8238
+ * labels already ARE the CamStack macros (Scrypted identity map).
8239
+ */
8240
+ classMap: ClassMapDefinitionSchema.optional()
8217
8241
  });
8218
8242
  var ConvertTargetSchema = discriminatedUnion("format", [object({
8219
8243
  format: literal("openvino"),
@@ -8242,7 +8266,8 @@ var ModelConvertMetadataSchema = object({
8242
8266
  "ocr",
8243
8267
  "segmentation"
8244
8268
  ]),
8245
- faceAlignment: boolean().optional()
8269
+ faceAlignment: boolean().optional(),
8270
+ classMap: ClassMapDefinitionSchema.optional()
8246
8271
  });
8247
8272
  var ConvertResultSchema = object({
8248
8273
  entry: ModelCatalogEntrySchema,
@@ -17301,6 +17326,33 @@ var NativeCropRefSchema = object({
17301
17326
  h: number()
17302
17327
  })
17303
17328
  });
17329
+ object({
17330
+ crop: object({
17331
+ left: number(),
17332
+ top: number(),
17333
+ width: number().positive(),
17334
+ height: number().positive()
17335
+ }).optional(),
17336
+ content: object({
17337
+ width: number().int().positive(),
17338
+ height: number().int().positive()
17339
+ }),
17340
+ fit: _enum(["stretch", "contain"]),
17341
+ format: _enum([
17342
+ "rgb",
17343
+ "gray",
17344
+ "jpeg"
17345
+ ])
17346
+ });
17347
+ var FrameRefSchema = object({
17348
+ registryId: string().min(1),
17349
+ id: string().min(1),
17350
+ width: number().int().positive(),
17351
+ height: number().int().positive(),
17352
+ format: _enum(["rgb", "gray"]),
17353
+ timestamp: number(),
17354
+ capturedAt: number().optional()
17355
+ });
17304
17356
  var ModelFormatSchema$1 = _enum([
17305
17357
  "onnx",
17306
17358
  "coreml",
@@ -17545,6 +17597,12 @@ method(_void(), array(PipelineEngineChoiceSchema)), method(_void(), PipelineEngi
17545
17597
  steps: array(PipelineStepInputSchema).min(1),
17546
17598
  frame: FrameInputSchema.optional(),
17547
17599
  /**
17600
+ * Process-local lazy frame. Valid only when caller and provider resolve
17601
+ * in the same execution-group process; split/cross-node callers use
17602
+ * `frame`/`image` inline compatibility instead.
17603
+ */
17604
+ frameRef: FrameRefSchema.optional(),
17605
+ /**
17548
17606
  * CB5 shm passthrough — a `FrameHandle` naming the same ring slot
17549
17607
  * the decoded pixels live in. One more member of the one-of
17550
17608
  * frame/frameHandle/image/imageBase64/referenceImage group.
@@ -17800,7 +17858,10 @@ var NativeCropResultSchema = object({
17800
17858
  * Which source served this crop, so a quality-sensitive consumer (the native
17801
17859
  * `keyFrame`) can reject a degraded fallback:
17802
17860
  * - `native` — cut from the decode worker's retained NATIVE surface (the
17803
- * quality path).
17861
+ * quality path). A subject-tile serve is also native-resolution and stays
17862
+ * `native` here: the public enum cannot name `tile` without a breaking cap
17863
+ * change. Runner telemetry distinguishes lease vs tile via `source` on the
17864
+ * internal crop result (`nativeHits` vs `tileHits`).
17804
17865
  * - `ram-fullframe` — the native surface MISSED but the request was full-frame,
17805
17866
  * so the ≤640 RAM `RetainedFrameStore` served it (honest lower-res; legit for
17806
17867
  * the blank-frame guard / 640-snapshot resolve, NOT for the clean keyFrame).
@@ -18291,12 +18352,41 @@ var RunnerLocalLoadSchema = object({
18291
18352
  * legacy `OrchestratorMetricsSchema` shape so existing dashboards keep
18292
18353
  * working unchanged when they switch to reading from the runner cap.
18293
18354
  */
18355
+ var FrameLazyCountersSchema = object({
18356
+ framesDecoded: number(),
18357
+ framesAdmitted: number(),
18358
+ framesDroppedPixelFree: number(),
18359
+ viewsMaterialized: number(),
18360
+ viewsSkipped: number(),
18361
+ workerToRunnerBytes: number(),
18362
+ runnerToPoolRawBytes: number(),
18363
+ runnerToPoolJpegBytes: number(),
18364
+ onDemandFullFrameRequests: number(),
18365
+ onDemandCropRequests: number(),
18366
+ nativeHits: number(),
18367
+ nativeMisses: number(),
18368
+ tileHits: number(),
18369
+ tileMisses: number(),
18370
+ fallbackHits: number(),
18371
+ fallbackMisses: number(),
18372
+ retainedWritesAvoided: number(),
18373
+ residentRefs: number(),
18374
+ residentBytes: number(),
18375
+ releases: number(),
18376
+ evictions: number(),
18377
+ staleMisses: number()
18378
+ });
18379
+ var FrameLazyMetricsSchema = object({
18380
+ node: FrameLazyCountersSchema,
18381
+ cameras: array(FrameLazyCountersSchema.extend({ deviceId: number() }))
18382
+ });
18294
18383
  var RunnerLocalMetricsSchema = object({
18295
18384
  nodeId: string(),
18296
18385
  activeCameras: number(),
18297
18386
  throttledCameras: number(),
18298
18387
  avgInferenceTimeMs: number(),
18299
- queueDepth: number()
18388
+ queueDepth: number(),
18389
+ frameLazy: FrameLazyMetricsSchema.optional()
18300
18390
  });
18301
18391
  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({
18302
18392
  handle: FrameHandleSchema,
@@ -19596,6 +19686,9 @@ method(_void(), ProviderInfoSchema), method(object({ config: record(string(), un
19596
19686
  location: StorageLocationSchema,
19597
19687
  relativePath: string()
19598
19688
  }), _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" });
19689
+ /** Profile-exported FormBuilder schema. Shape is ConfigUISchema at the UI. */
19690
+ var ProfileSettingsSchemaBridge = unknown().nullable();
19691
+ var ProfileSettingsBagSchema = record(string(), unknown());
19599
19692
  /**
19600
19693
  * A live terminal session hosted by the provider addon. Output and input do
19601
19694
  * NOT flow through the capability — they use the addon data plane
@@ -19625,7 +19718,14 @@ var TerminalSessionInfoSchema = object({
19625
19718
  var TerminalProfileInfoSchema = object({
19626
19719
  profileId: string(),
19627
19720
  label: string(),
19628
- description: string().optional()
19721
+ description: string().optional(),
19722
+ /** Spawn defaults the instance form copies on create. */
19723
+ executable: string().optional(),
19724
+ args: array(string()).readonly().optional(),
19725
+ cwd: string().optional(),
19726
+ environment: array(string()).readonly().optional(),
19727
+ /** ConfigUISchema for instance knobs, or null when the profile has none. */
19728
+ settingsSchema: ProfileSettingsSchemaBridge.optional()
19629
19729
  });
19630
19730
  /**
19631
19731
  * A durable operator-created Terminal instance. Profiles are templates; only
@@ -19638,7 +19738,12 @@ var TerminalInstanceInfoSchema = object({
19638
19738
  profileId: string(),
19639
19739
  profileLabel: string(),
19640
19740
  name: string(),
19641
- enabled: boolean()
19741
+ enabled: boolean(),
19742
+ executable: string(),
19743
+ args: array(string()).readonly(),
19744
+ cwd: string(),
19745
+ environment: array(string()).readonly(),
19746
+ profileSettings: ProfileSettingsBagSchema
19642
19747
  });
19643
19748
  var TerminalLegacyCameraSchema = object({
19644
19749
  stableId: string(),
@@ -19668,7 +19773,23 @@ var TerminalOutputBatchSchema = object({
19668
19773
  method(_void(), array(TerminalProfileInfoSchema).readonly(), { auth: "admin" }), method(_void(), array(TerminalInstanceInfoSchema).readonly(), { auth: "admin" }), method(object({
19669
19774
  targetNodeId: string().min(1),
19670
19775
  profileId: string().min(1),
19671
- name: string().trim().min(1).max(160).optional()
19776
+ name: string().trim().min(1).max(160).optional(),
19777
+ executable: string().max(1024).optional(),
19778
+ args: array(string().max(2048)).max(64).optional(),
19779
+ cwd: string().max(1024).optional(),
19780
+ environment: array(string().max(4096)).max(64).optional(),
19781
+ profileSettings: ProfileSettingsBagSchema.optional()
19782
+ }), TerminalInstanceInfoSchema, {
19783
+ kind: "mutation",
19784
+ auth: "admin"
19785
+ }), method(object({
19786
+ instanceId: string().min(1),
19787
+ name: string().trim().min(1).max(160).optional(),
19788
+ executable: string().max(1024).optional(),
19789
+ args: array(string().max(2048)).max(64).optional(),
19790
+ cwd: string().max(1024).optional(),
19791
+ environment: array(string().max(4096)).max(64).optional(),
19792
+ profileSettings: ProfileSettingsBagSchema.optional()
19672
19793
  }), TerminalInstanceInfoSchema, {
19673
19794
  kind: "mutation",
19674
19795
  auth: "admin"
@@ -19690,7 +19811,11 @@ method(_void(), array(TerminalProfileInfoSchema).readonly(), { auth: "admin" }),
19690
19811
  }), method(_void(), array(TerminalSessionInfoSchema).readonly(), { auth: "admin" }), method(object({
19691
19812
  profileId: string(),
19692
19813
  cols: number().int().positive(),
19693
- rows: number().int().positive()
19814
+ rows: number().int().positive(),
19815
+ executable: string().max(1024).optional(),
19816
+ args: array(string().max(2048)).max(64).optional(),
19817
+ cwd: string().max(1024).optional(),
19818
+ environment: array(string().max(4096)).max(64).optional()
19694
19819
  }), TerminalSessionInfoSchema, {
19695
19820
  kind: "mutation",
19696
19821
  auth: "admin"
@@ -22472,10 +22597,10 @@ DeviceType.LawnMower, method(object({ deviceId: number().int().nonnegative() }),
22472
22597
  *
22473
22598
  * • The SDK (mobile / web client) consumes `getConnectionEndpoints()`
22474
22599
  * to receive an ordered list of candidate base URLs it should race
22475
- * on connect — LAN IPv4 first (lowest latency when on same network),
22476
- * then public hostname (if a tunnel is up), then IPv6. The SDK can
22477
- * race them with short timeouts and stick with the winner for the
22478
- * session.
22600
+ * on connect — LAN IPv4 and stable LAN IPv6 first (lowest latency
22601
+ * when on the same network), then public hostname (if a tunnel is
22602
+ * up). The SDK can race them with short timeouts and stick with the
22603
+ * winner for the session.
22479
22604
  *
22480
22605
  * Why hub-only: agents are not directly addressable by the operator's
22481
22606
  * clients — they reverse-connect to the hub. Exposing their interfaces
@@ -22630,6 +22755,17 @@ var NotificationEndpointSchema = object({
22630
22755
  /** What the ranking currently resolves to (null when nothing is reachable). */
22631
22756
  resolved: string().nullable()
22632
22757
  });
22758
+ /**
22759
+ * The URLs the SDK / viewer should race for API access. `baseUrls` empty =
22760
+ * AUTO (every LAN IPv4 + the public tunnel). `resolved` is what that choice
22761
+ * currently expands to, so the UI can show the effective set either way.
22762
+ */
22763
+ var ViewerEndpointsSchema = object({
22764
+ /** The operator's explicit race set, or empty for AUTO. */
22765
+ baseUrls: array(string()).readonly(),
22766
+ /** What the ranking currently resolves to (may be empty if nothing is up). */
22767
+ resolved: array(string()).readonly()
22768
+ });
22633
22769
  var AllowedAddressesSchema = object({
22634
22770
  /**
22635
22771
  * Allowlist of interface addresses operators have explicitly opted
@@ -22638,6 +22774,20 @@ var AllowedAddressesSchema = object({
22638
22774
  * Network Addresses admin page and persisted by the addon.
22639
22775
  */
22640
22776
  addresses: array(string()).readonly() });
22777
+ var TlsStatusSchema = object({
22778
+ mode: _enum([
22779
+ "generated",
22780
+ "uploaded",
22781
+ "disabled"
22782
+ ]),
22783
+ leafFingerprintSha256: string().nullable(),
22784
+ caFingerprintSha256: string().nullable(),
22785
+ validTo: string().nullable(),
22786
+ sans: array(string()),
22787
+ caCertPem: string().nullable(),
22788
+ reissueError: string().nullable(),
22789
+ restartRequired: boolean()
22790
+ });
22641
22791
  method(_void(), ListResultSchema), method(_void(), PreferredSchema), method(object({
22642
22792
  /**
22643
22793
  * LEGACY HINT — do not send from new code. Kept optional so clients
@@ -22647,17 +22797,31 @@ method(_void(), ListResultSchema), method(_void(), PreferredSchema), method(obje
22647
22797
  */
22648
22798
  port: number().int().min(1).max(65535).optional(),
22649
22799
  /** Include `http(s)://127.0.0.1:<port>` as the lowest-priority
22650
- * candidate. Default `true`. */
22800
+ * candidate. Default `false` — loopback is not a client route. */
22651
22801
  includeLoopback: boolean().optional(),
22652
- /** Skip IPv6 entries. Some legacy clients can't parse them.
22653
- * Default `false`. */
22802
+ /** Skip IPv6 entries. Default `false` the palette includes stable
22803
+ * LAN IPv6 (ICE/WebRTC uses dual-stack regardless). Pass `true` to
22804
+ * hide them. The viewer HTTP/WS race is `getViewerEndpoints`. */
22654
22805
  ipv4Only: boolean().optional(),
22655
22806
  /** Scheme to emit for LAN/loopback URLs. Default `'http'`.
22656
22807
  * Pass `'https'` when the caller is itself loaded over HTTPS
22657
22808
  * to avoid mixed-content blocks in the browser. The public
22658
22809
  * tunnel always emits `https://` regardless. */
22659
22810
  scheme: _enum(["http", "https"]).optional()
22660
- }), 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" });
22811
+ }), 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, {
22812
+ kind: "mutation",
22813
+ auth: "admin"
22814
+ }), method(object({
22815
+ certPem: string().min(1),
22816
+ keyPem: string().min(1),
22817
+ caPem: string().optional()
22818
+ }), TlsStatusSchema, {
22819
+ kind: "mutation",
22820
+ auth: "admin"
22821
+ }), method(_void(), object({ pem: string() })), method(_void(), TlsStatusSchema, {
22822
+ kind: "mutation",
22823
+ auth: "admin"
22824
+ });
22661
22825
  object({
22662
22826
  /** Lifecycle state of the lock. `jammed` means the motor reported
22663
22827
  * failure to reach the target — operator intervention required. */
@@ -28420,6 +28584,12 @@ Object.freeze({
28420
28584
  addonId: null,
28421
28585
  access: "create"
28422
28586
  },
28587
+ "localNetwork.downloadCa": {
28588
+ capName: "local-network",
28589
+ capScope: "system",
28590
+ addonId: null,
28591
+ access: "view"
28592
+ },
28423
28593
  "localNetwork.getAllowedAddresses": {
28424
28594
  capName: "local-network",
28425
28595
  capScope: "system",
@@ -28444,18 +28614,42 @@ Object.freeze({
28444
28614
  addonId: null,
28445
28615
  access: "view"
28446
28616
  },
28617
+ "localNetwork.getTlsStatus": {
28618
+ capName: "local-network",
28619
+ capScope: "system",
28620
+ addonId: null,
28621
+ access: "view"
28622
+ },
28623
+ "localNetwork.getViewerEndpoints": {
28624
+ capName: "local-network",
28625
+ capScope: "system",
28626
+ addonId: null,
28627
+ access: "view"
28628
+ },
28447
28629
  "localNetwork.list": {
28448
28630
  capName: "local-network",
28449
28631
  capScope: "system",
28450
28632
  addonId: null,
28451
28633
  access: "view"
28452
28634
  },
28635
+ "localNetwork.regenerateCertificate": {
28636
+ capName: "local-network",
28637
+ capScope: "system",
28638
+ addonId: null,
28639
+ access: "create"
28640
+ },
28453
28641
  "localNetwork.resetAllowlistToBestMatch": {
28454
28642
  capName: "local-network",
28455
28643
  capScope: "system",
28456
28644
  addonId: null,
28457
28645
  access: "delete"
28458
28646
  },
28647
+ "localNetwork.revertToGeneratedCertificate": {
28648
+ capName: "local-network",
28649
+ capScope: "system",
28650
+ addonId: null,
28651
+ access: "create"
28652
+ },
28459
28653
  "localNetwork.setAllowedAddresses": {
28460
28654
  capName: "local-network",
28461
28655
  capScope: "system",
@@ -28468,6 +28662,18 @@ Object.freeze({
28468
28662
  addonId: null,
28469
28663
  access: "create"
28470
28664
  },
28665
+ "localNetwork.setViewerEndpoints": {
28666
+ capName: "local-network",
28667
+ capScope: "system",
28668
+ addonId: null,
28669
+ access: "create"
28670
+ },
28671
+ "localNetwork.uploadCertificate": {
28672
+ capName: "local-network",
28673
+ capScope: "system",
28674
+ addonId: null,
28675
+ access: "create"
28676
+ },
28471
28677
  "lockControl.lock": {
28472
28678
  capName: "lock-control",
28473
28679
  capScope: "device",
@@ -31348,6 +31554,12 @@ Object.freeze({
31348
31554
  addonId: null,
31349
31555
  access: "create"
31350
31556
  },
31557
+ "terminalSession.updateInstance": {
31558
+ capName: "terminal-session",
31559
+ capScope: "system",
31560
+ addonId: null,
31561
+ access: "create"
31562
+ },
31351
31563
  "terminalSession.writeInput": {
31352
31564
  capName: "terminal-session",
31353
31565
  capScope: "system",
@@ -33835,6 +34047,35 @@ Object.freeze(Object.fromEntries([{
33835
34047
  }]
33836
34048
  }].map((s) => [s.stepId, s.defaultModelId])));
33837
34049
  string().min(1);
34050
+ var CLUSTER_STEP_SETTING_FIELDS = [{
34051
+ stepId: "face-embedding",
34052
+ key: "minLandmarkFaceSize",
34053
+ label: "Min face size for recognition (detection px)",
34054
+ 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.",
34055
+ type: "slider",
34056
+ min: 0,
34057
+ max: 64,
34058
+ step: 2,
34059
+ default: 24
34060
+ }];
34061
+ function clusterStepSettingKey(stepId, fieldKey) {
34062
+ return `clusterStepSetting:${stepId}:${fieldKey}`;
34063
+ }
34064
+ var ClusterSettingNumberSchema = number().finite();
34065
+ function readClusterStepSettings(config) {
34066
+ const out = {};
34067
+ for (const field of CLUSTER_STEP_SETTING_FIELDS) {
34068
+ const parsed = ClusterSettingNumberSchema.safeParse(config[clusterStepSettingKey(field.stepId, field.key)]);
34069
+ const value = parsed.success ? parsed.data : field.default;
34070
+ const existing = out[field.stepId] ?? {};
34071
+ out[field.stepId] = {
34072
+ ...existing,
34073
+ [field.key]: value
34074
+ };
34075
+ }
34076
+ return out;
34077
+ }
34078
+ readClusterStepSettings({});
33838
34079
  object({
33839
34080
  /**
33840
34081
  * 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-mqtt-broker",
3
- "version": "1.2.25",
3
+ "version": "1.2.27",
4
4
  "description": "MQTT broker registry addon for CamStack — manages external broker entries + an optional embedded aedes broker. Consumers spin up their own `mqtt.js` clients via the `mqtt-broker` cap.",
5
5
  "keywords": [
6
6
  "camstack",