@camstack/addon-smtp-nodemailer 1.2.68 → 1.2.70

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.
@@ -36,7 +36,7 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
36
36
  }) : target, mod));
37
37
  var __toCommonJS = (mod) => __hasOwnProp.call(mod, "module.exports") ? mod["module.exports"] : __copyProps(__defProp({}, "__esModule", { value: true }), mod);
38
38
  //#endregion
39
- //#region ../types/dist/event-category-BZL-fdNj.mjs
39
+ //#region ../types/dist/event-category-zAv7pMUz.mjs
40
40
  var EventCategory = /* @__PURE__ */ function(EventCategory) {
41
41
  EventCategory["SystemBoot"] = "system.boot";
42
42
  EventCategory["SystemAddonsReady"] = "system.addons-ready";
@@ -408,6 +408,13 @@ var EventCategory = /* @__PURE__ */ function(EventCategory) {
408
408
  */
409
409
  EventCategory["BatteryOnStatusChanged"] = "battery.onStatusChanged";
410
410
  /**
411
+ * Cap event fired by every device that registers the `network-link`
412
+ * capability. Mirrors the cap definition's `onStatusChanged`. Carries
413
+ * `{ deviceId, status: NetworkLinkStatus }` — a link switch or a signal
414
+ * reading that moved.
415
+ */
416
+ EventCategory["NetworkLinkOnStatusChanged"] = "network-link.onStatusChanged";
417
+ /**
411
418
  * Emitted by the battery cap provider WHEN `wakeForStream` enters the
412
419
  * "wake in progress" window — between the Baichuan wake-up issue and
413
420
  * the camera's first dialed-back RTP packet. The stream-broker
@@ -23215,6 +23222,39 @@ DeviceType.Camera, DeviceType.Sensor, DeviceType.Button, DeviceType.Switch, meth
23215
23222
  deviceId: number(),
23216
23223
  status: BatteryStatusSchema
23217
23224
  });
23225
+ /**
23226
+ * Network-link snapshot. Same shape for every provider (a Reolink wifi
23227
+ * camera, a Home Assistant device with a signal-strength sensor, a Tapo
23228
+ * plug): one slice under `device.runtimeState['network-link']`, one badge,
23229
+ * one Home Assistant projection.
23230
+ */
23231
+ var NetworkLinkStatusSchema = object({
23232
+ /** The link the device is on. `'unknown'` = not read yet, not "no link". */
23233
+ type: _enum([
23234
+ "wifi",
23235
+ "ethernet",
23236
+ "cellular",
23237
+ "unknown"
23238
+ ]),
23239
+ /**
23240
+ * Link quality, 0..100 inclusive, normalised by the provider from whatever
23241
+ * the firmware reports (bars, RSSI, a vendor scale). **`null` means NOT
23242
+ * KNOWN or NOT APPLICABLE** — a wired link has no signal, and a wireless
23243
+ * one whose reading has not landed must not be drawn at 0 %. Consumers
23244
+ * SKIP a null rather than coerce it.
23245
+ */
23246
+ signalPercent: number().min(0).max(100).nullable(),
23247
+ /** Raw received signal strength in dBm, when the firmware reports one. */
23248
+ rssiDbm: number().optional(),
23249
+ /** Network name of a wireless link, when the firmware reports it. */
23250
+ ssid: string().optional(),
23251
+ /** Ms epoch of the last observation. Lets consumers reason about freshness. */
23252
+ lastUpdated: number()
23253
+ });
23254
+ DeviceType.Camera, DeviceType.Sensor, DeviceType.Button, DeviceType.Switch, DeviceType.Light, DeviceType.Lock, DeviceType.Siren, object({
23255
+ deviceId: number(),
23256
+ status: NetworkLinkStatusSchema
23257
+ });
23218
23258
  object({
23219
23259
  on: boolean(),
23220
23260
  /** Ms epoch of the last transition. 0 if never observed. */
@@ -26809,6 +26849,203 @@ DeviceType.Camera, method(object({ deviceId: number() }), PtzAutotrackStatusSche
26809
26849
  deviceId: number(),
26810
26850
  status: PtzAutotrackStatusSchema
26811
26851
  });
26852
+ /**
26853
+ * `navigation` — a device-scoped capability that natively expresses the FULL
26854
+ * navigation / action surface of a robot that DRIVES ITSELF and carries an
26855
+ * on-board camera (the Dreame robot-vacuum camera is the first provider).
26856
+ *
26857
+ * Why a NEW cap rather than overloading `ptz`:
26858
+ * - `ptz` moves a *gimbal* on a fixed camera (pan/tilt/zoom of the lens). A
26859
+ * robot vacuum has no gimbal — the whole chassis drives, turns and spins.
26860
+ * The two are different physical models: PTZ is absolute-position + presets,
26861
+ * navigation is momentary drive nudges + discrete robot ACTIONS
26862
+ * (dock / spot-clean / follow-pet / go-to-point / …).
26863
+ * - This cap is the SOURCE OF TRUTH. Two consumers adapt from it rather than
26864
+ * the reverse:
26865
+ * 1. a native CamStack navigation panel (data-driven from `listActions`
26866
+ * / `getOptions`), and
26867
+ * 2. the PTZ surface — a thin adapter mimics `ptz` from `navigation` so a
26868
+ * robot camera shows up in the existing PTZ control path without every
26869
+ * PTZ provider learning about robots. The mapping lives in the adapter,
26870
+ * not here (see the addon design note):
26871
+ * ptz.continuousMove({pan,tilt}) → navigation.move({pan,tilt})
26872
+ * ptz.stop() → navigation.stop()
26873
+ * ptz.goHome() → navigation.runAction('goHome')
26874
+ * ptz.getPresets() → navigation.listActions() (id→preset)
26875
+ * ptz.goToPreset(id) → navigation.runAction(id)
26876
+ *
26877
+ * ## Continuous drive
26878
+ *
26879
+ * `move` is MOMENTARY. Fluid navigation comes from the UI (or the PTZ adapter)
26880
+ * sending `move({pan,tilt})` REPEATEDLY at ~1 Hz while a direction is held, and
26881
+ * one `stop()` on release — exactly like the robot app's remote-drive joystick.
26882
+ * The provider forwards EACH `move` to one drive write; it must NOT debounce or
26883
+ * coalesce them. The UI owns the cadence.
26884
+ *
26885
+ * ## The action dictionary
26886
+ *
26887
+ * The discrete controls (dock / locate / spot-clean / follow / flash / sounds)
26888
+ * are a DATA-DRIVEN dictionary the cap exposes via `listActions()`. Each entry
26889
+ * carries `{ id, kind, label, icon }` (plus `soundId` for sound entries) so the
26890
+ * native panel AND the PTZ mimic render buttons WITHOUT hardcoding a
26891
+ * vendor-specific list. `kind: 'action'` entries are triggered with
26892
+ * `runAction({ actionId })`; `kind: 'sound'` entries with `playSound({ soundId })`
26893
+ * (the entry carries the `soundId` to pass). The general primitives — `move`,
26894
+ * `stop`, `goToPoint` — stay as first-class methods, not dictionary entries.
26895
+ *
26896
+ * NOTE (provider wiring): the first provider (Dreame) drives this with the RAW
26897
+ * `callAction(siid,aiid,in)` / `setProperty({siid,piid,value})` MIoT primitives
26898
+ * that the currently-published `@apocaliss92/nodedreame` already exposes on
26899
+ * every device handle. A future nodedreame publish adds a typed
26900
+ * `DreameCameraController` (drive / playPetSound / spotClean / findPet / …); the
26901
+ * provider can then swap the raw calls for the typed methods with no change to
26902
+ * THIS contract.
26903
+ */
26904
+ /**
26905
+ * A momentary drive nudge. The robot MOVES (no gimbal) for as long as the caller
26906
+ * keeps sending nudges (~1 Hz); an explicit `stop` (or letting the nudges lapse)
26907
+ * halts it.
26908
+ *
26909
+ * - `pan` — turn: negative = left, positive = right, 0 = straight.
26910
+ * - `tilt` — throttle: positive = forward, negative = spin / turn-around.
26911
+ * - `speed` — optional intensity hint [0, 1]; the provider may scale the drive
26912
+ * vector by it (drivers without proportional drive ignore it).
26913
+ *
26914
+ * `pan` / `tilt` are normalized [-1, 1]. Both optional so a caller can nudge one
26915
+ * axis alone; an all-undefined nudge is a no-op.
26916
+ */
26917
+ var NavigationMoveCommandSchema = object({
26918
+ pan: number().min(-1).max(1).optional(),
26919
+ tilt: number().min(-1).max(1).optional(),
26920
+ speed: number().min(0).max(1).optional()
26921
+ });
26922
+ /**
26923
+ * The enumerated discrete actions a navigation-capable robot can perform via
26924
+ * `runAction`. This is the CLOSED vocabulary; a given device advertises the
26925
+ * subset it supports through `listActions`. Sounds are NOT here — they go through
26926
+ * `playSound` (see the `sound` dictionary entries).
26927
+ */
26928
+ var NavigationActionIdSchema = _enum([
26929
+ "goHome",
26930
+ "locate",
26931
+ "spotClean",
26932
+ "findPet",
26933
+ "personFollow",
26934
+ "stop",
26935
+ "startClean",
26936
+ "pauseClean",
26937
+ "dockWash",
26938
+ "autoEmpty",
26939
+ "flashOn",
26940
+ "flashOff"
26941
+ ]);
26942
+ /** Whether a dictionary entry is a `runAction` action or a `playSound` sound. */
26943
+ var NavigationEntryKindSchema = _enum(["action", "sound"]);
26944
+ /**
26945
+ * One entry in the navigation action dictionary — the DATA-DRIVEN unit both the
26946
+ * native panel and the PTZ mimic render as a button.
26947
+ *
26948
+ * - `id` — stable id. For `kind:'action'` it is a {@link NavigationActionId}
26949
+ * (pass to `runAction`); for `kind:'sound'` it is a namespaced id
26950
+ * (`sound:meow`) whose `soundId` is passed to `playSound`.
26951
+ * - `icon` — icon HINT (lucide-style name; the UI maps it to its own set).
26952
+ * - `label` — operator-facing English label.
26953
+ * - `soundId` — wire sound id, present only on `kind:'sound'` entries.
26954
+ * - `enabled` — per-device FEATURE FLAG. `listActions` reports it so the UI /
26955
+ * PTZ render ONLY enabled entries. Data-driven: the provider
26956
+ * flips it from config, never by editing code.
26957
+ */
26958
+ var NavigationActionEntrySchema = object({
26959
+ id: string(),
26960
+ kind: NavigationEntryKindSchema,
26961
+ label: string(),
26962
+ icon: string(),
26963
+ /** Present only on `kind:'sound'` entries — the id to pass to `playSound`. */
26964
+ soundId: number().int().optional(),
26965
+ /** Per-device feature flag — render this entry only when true. */
26966
+ enabled: boolean()
26967
+ });
26968
+ /** Coordinates for `goToPoint` — a point on the robot's live map. */
26969
+ var NavigationPointSchema = object({
26970
+ x: number(),
26971
+ y: number()
26972
+ });
26973
+ /**
26974
+ * Per-device FEATURE-FLAG report for the general (non-dictionary) primitives.
26975
+ * The cap reports which are enabled so the UI / PTZ render only the controls
26976
+ * that are turned on for THIS device. Data-driven: the provider derives these
26977
+ * from config + probe, never hardcoded in the UI. The per-DICTIONARY-entry flags
26978
+ * live on {@link NavigationActionEntrySchema.enabled}; these gate the primitives
26979
+ * that are not dictionary entries.
26980
+ *
26981
+ * - `move` / `stop` — the momentary drive joystick.
26982
+ * - `goToPoint` — send-to-map-coordinate. Ships OFF on Dreame until the
26983
+ * map-coordinate plumbing is wired.
26984
+ * - `runAction` — the discrete action buttons (dictionary `kind:'action'`).
26985
+ * - `playSound` — the sound buttons (dictionary `kind:'sound'`).
26986
+ * - `light` — the on/off fill-light toggle (works anytime).
26987
+ * - `lightMode` — the auto/manual selector + manual level slider (a
26988
+ * camera-service control; needs an active stream).
26989
+ */
26990
+ var NavigationFeaturesSchema = object({
26991
+ move: boolean(),
26992
+ stop: boolean(),
26993
+ goToPoint: boolean(),
26994
+ runAction: boolean(),
26995
+ playSound: boolean(),
26996
+ light: boolean(),
26997
+ lightMode: boolean()
26998
+ });
26999
+ /** Light mode: `auto` lets the camera choose brightness; `manual` uses `level`. */
27000
+ var NavigationLightModeSchema = _enum(["auto", "manual"]);
27001
+ /**
27002
+ * Live navigation state so the UI can reflect what the robot is doing:
27003
+ * - `mode` — coarse activity (idle / cleaning / following / …).
27004
+ * - `following` — person/pet follow is currently armed.
27005
+ * - `flash` — the on-camera fill light is on.
27006
+ * - `lightMode` — auto vs manual fill-light mode.
27007
+ * - `lightLevel` — manual fill-light level (40..100); meaningful when
27008
+ * `lightMode === 'manual'`.
27009
+ */
27010
+ var NavigationStatusSchema = object({
27011
+ mode: _enum([
27012
+ "idle",
27013
+ "cleaning",
27014
+ "spot",
27015
+ "following",
27016
+ "goto",
27017
+ "returning",
27018
+ "paused",
27019
+ "unknown"
27020
+ ]),
27021
+ following: boolean(),
27022
+ flash: boolean(),
27023
+ lightMode: NavigationLightModeSchema,
27024
+ lightLevel: number().min(40).max(100),
27025
+ /** Ms epoch when the slice was last updated. */
27026
+ lastChangedAt: number()
27027
+ });
27028
+ NavigationStatusSchema.extend({ lastFetchedAt: number() });
27029
+ DeviceType.Camera, method(NavigationMoveCommandSchema.extend({ deviceId: number() }), _void(), { kind: "mutation" }), method(object({ deviceId: number() }), _void(), { kind: "mutation" }), method(NavigationPointSchema.extend({ deviceId: number() }), _void(), { kind: "mutation" }), method(object({ deviceId: number() }), array(NavigationActionEntrySchema)), method(object({
27030
+ deviceId: number(),
27031
+ actionId: NavigationActionIdSchema
27032
+ }), _void(), { kind: "mutation" }), method(object({
27033
+ deviceId: number(),
27034
+ soundId: number().int()
27035
+ }), _void(), { kind: "mutation" }), method(object({
27036
+ deviceId: number(),
27037
+ on: boolean()
27038
+ }), _void(), { kind: "mutation" }), method(object({
27039
+ deviceId: number(),
27040
+ mode: NavigationLightModeSchema,
27041
+ level: number().min(40).max(100).optional()
27042
+ }), _void(), { kind: "mutation" }), method(object({
27043
+ deviceId: number(),
27044
+ level: number().min(40).max(100)
27045
+ }), _void(), { kind: "mutation" }), method(object({ deviceId: number() }), NavigationFeaturesSchema), object({
27046
+ deviceId: number(),
27047
+ status: NavigationStatusSchema
27048
+ });
26812
27049
  DeviceType.Camera, DeviceType.Sensor, DeviceType.Switch, method(object({ deviceId: number().int().nonnegative() }), object({ success: literal(true) }), {
26813
27050
  kind: "mutation",
26814
27051
  auth: "admin"
@@ -31873,6 +32110,66 @@ Object.freeze({
31873
32110
  addonId: null,
31874
32111
  access: "create"
31875
32112
  },
32113
+ "navigation.getFeatures": {
32114
+ capName: "navigation",
32115
+ capScope: "device",
32116
+ addonId: null,
32117
+ access: "view"
32118
+ },
32119
+ "navigation.goToPoint": {
32120
+ capName: "navigation",
32121
+ capScope: "device",
32122
+ addonId: null,
32123
+ access: "create"
32124
+ },
32125
+ "navigation.listActions": {
32126
+ capName: "navigation",
32127
+ capScope: "device",
32128
+ addonId: null,
32129
+ access: "view"
32130
+ },
32131
+ "navigation.move": {
32132
+ capName: "navigation",
32133
+ capScope: "device",
32134
+ addonId: null,
32135
+ access: "create"
32136
+ },
32137
+ "navigation.playSound": {
32138
+ capName: "navigation",
32139
+ capScope: "device",
32140
+ addonId: null,
32141
+ access: "create"
32142
+ },
32143
+ "navigation.runAction": {
32144
+ capName: "navigation",
32145
+ capScope: "device",
32146
+ addonId: null,
32147
+ access: "create"
32148
+ },
32149
+ "navigation.setLightLevel": {
32150
+ capName: "navigation",
32151
+ capScope: "device",
32152
+ addonId: null,
32153
+ access: "create"
32154
+ },
32155
+ "navigation.setLightMode": {
32156
+ capName: "navigation",
32157
+ capScope: "device",
32158
+ addonId: null,
32159
+ access: "create"
32160
+ },
32161
+ "navigation.setLightOn": {
32162
+ capName: "navigation",
32163
+ capScope: "device",
32164
+ addonId: null,
32165
+ access: "create"
32166
+ },
32167
+ "navigation.stop": {
32168
+ capName: "navigation",
32169
+ capScope: "device",
32170
+ addonId: null,
32171
+ access: "create"
32172
+ },
31876
32173
  "networkAccess.getEndpoint": {
31877
32174
  capName: "network-access",
31878
32175
  capScope: "system",
@@ -35968,6 +36265,56 @@ Object.freeze({
35968
36265
  form: "single",
35969
36266
  optional: false
35970
36267
  }],
36268
+ "navigation.getFeatures": [{
36269
+ name: "deviceId",
36270
+ form: "single",
36271
+ optional: false
36272
+ }],
36273
+ "navigation.goToPoint": [{
36274
+ name: "deviceId",
36275
+ form: "single",
36276
+ optional: false
36277
+ }],
36278
+ "navigation.listActions": [{
36279
+ name: "deviceId",
36280
+ form: "single",
36281
+ optional: false
36282
+ }],
36283
+ "navigation.move": [{
36284
+ name: "deviceId",
36285
+ form: "single",
36286
+ optional: false
36287
+ }],
36288
+ "navigation.playSound": [{
36289
+ name: "deviceId",
36290
+ form: "single",
36291
+ optional: false
36292
+ }],
36293
+ "navigation.runAction": [{
36294
+ name: "deviceId",
36295
+ form: "single",
36296
+ optional: false
36297
+ }],
36298
+ "navigation.setLightLevel": [{
36299
+ name: "deviceId",
36300
+ form: "single",
36301
+ optional: false
36302
+ }],
36303
+ "navigation.setLightMode": [{
36304
+ name: "deviceId",
36305
+ form: "single",
36306
+ optional: false
36307
+ }],
36308
+ "navigation.setLightOn": [{
36309
+ name: "deviceId",
36310
+ form: "single",
36311
+ optional: false
36312
+ }],
36313
+ "navigation.stop": [{
36314
+ name: "deviceId",
36315
+ form: "single",
36316
+ optional: false
36317
+ }],
35971
36318
  "networkQuality.getDeviceStats": [{
35972
36319
  name: "deviceId",
35973
36320
  form: "single",
@@ -34,7 +34,7 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
34
34
  var __toCommonJS = (mod) => __hasOwnProp.call(mod, "module.exports") ? mod["module.exports"] : __copyProps(__defProp({}, "__esModule", { value: true }), mod);
35
35
  var __require = /* @__PURE__ */ createRequire(import.meta.url);
36
36
  //#endregion
37
- //#region ../types/dist/event-category-BZL-fdNj.mjs
37
+ //#region ../types/dist/event-category-zAv7pMUz.mjs
38
38
  var EventCategory = /* @__PURE__ */ function(EventCategory) {
39
39
  EventCategory["SystemBoot"] = "system.boot";
40
40
  EventCategory["SystemAddonsReady"] = "system.addons-ready";
@@ -406,6 +406,13 @@ var EventCategory = /* @__PURE__ */ function(EventCategory) {
406
406
  */
407
407
  EventCategory["BatteryOnStatusChanged"] = "battery.onStatusChanged";
408
408
  /**
409
+ * Cap event fired by every device that registers the `network-link`
410
+ * capability. Mirrors the cap definition's `onStatusChanged`. Carries
411
+ * `{ deviceId, status: NetworkLinkStatus }` — a link switch or a signal
412
+ * reading that moved.
413
+ */
414
+ EventCategory["NetworkLinkOnStatusChanged"] = "network-link.onStatusChanged";
415
+ /**
409
416
  * Emitted by the battery cap provider WHEN `wakeForStream` enters the
410
417
  * "wake in progress" window — between the Baichuan wake-up issue and
411
418
  * the camera's first dialed-back RTP packet. The stream-broker
@@ -23213,6 +23220,39 @@ DeviceType.Camera, DeviceType.Sensor, DeviceType.Button, DeviceType.Switch, meth
23213
23220
  deviceId: number(),
23214
23221
  status: BatteryStatusSchema
23215
23222
  });
23223
+ /**
23224
+ * Network-link snapshot. Same shape for every provider (a Reolink wifi
23225
+ * camera, a Home Assistant device with a signal-strength sensor, a Tapo
23226
+ * plug): one slice under `device.runtimeState['network-link']`, one badge,
23227
+ * one Home Assistant projection.
23228
+ */
23229
+ var NetworkLinkStatusSchema = object({
23230
+ /** The link the device is on. `'unknown'` = not read yet, not "no link". */
23231
+ type: _enum([
23232
+ "wifi",
23233
+ "ethernet",
23234
+ "cellular",
23235
+ "unknown"
23236
+ ]),
23237
+ /**
23238
+ * Link quality, 0..100 inclusive, normalised by the provider from whatever
23239
+ * the firmware reports (bars, RSSI, a vendor scale). **`null` means NOT
23240
+ * KNOWN or NOT APPLICABLE** — a wired link has no signal, and a wireless
23241
+ * one whose reading has not landed must not be drawn at 0 %. Consumers
23242
+ * SKIP a null rather than coerce it.
23243
+ */
23244
+ signalPercent: number().min(0).max(100).nullable(),
23245
+ /** Raw received signal strength in dBm, when the firmware reports one. */
23246
+ rssiDbm: number().optional(),
23247
+ /** Network name of a wireless link, when the firmware reports it. */
23248
+ ssid: string().optional(),
23249
+ /** Ms epoch of the last observation. Lets consumers reason about freshness. */
23250
+ lastUpdated: number()
23251
+ });
23252
+ DeviceType.Camera, DeviceType.Sensor, DeviceType.Button, DeviceType.Switch, DeviceType.Light, DeviceType.Lock, DeviceType.Siren, object({
23253
+ deviceId: number(),
23254
+ status: NetworkLinkStatusSchema
23255
+ });
23216
23256
  object({
23217
23257
  on: boolean(),
23218
23258
  /** Ms epoch of the last transition. 0 if never observed. */
@@ -26807,6 +26847,203 @@ DeviceType.Camera, method(object({ deviceId: number() }), PtzAutotrackStatusSche
26807
26847
  deviceId: number(),
26808
26848
  status: PtzAutotrackStatusSchema
26809
26849
  });
26850
+ /**
26851
+ * `navigation` — a device-scoped capability that natively expresses the FULL
26852
+ * navigation / action surface of a robot that DRIVES ITSELF and carries an
26853
+ * on-board camera (the Dreame robot-vacuum camera is the first provider).
26854
+ *
26855
+ * Why a NEW cap rather than overloading `ptz`:
26856
+ * - `ptz` moves a *gimbal* on a fixed camera (pan/tilt/zoom of the lens). A
26857
+ * robot vacuum has no gimbal — the whole chassis drives, turns and spins.
26858
+ * The two are different physical models: PTZ is absolute-position + presets,
26859
+ * navigation is momentary drive nudges + discrete robot ACTIONS
26860
+ * (dock / spot-clean / follow-pet / go-to-point / …).
26861
+ * - This cap is the SOURCE OF TRUTH. Two consumers adapt from it rather than
26862
+ * the reverse:
26863
+ * 1. a native CamStack navigation panel (data-driven from `listActions`
26864
+ * / `getOptions`), and
26865
+ * 2. the PTZ surface — a thin adapter mimics `ptz` from `navigation` so a
26866
+ * robot camera shows up in the existing PTZ control path without every
26867
+ * PTZ provider learning about robots. The mapping lives in the adapter,
26868
+ * not here (see the addon design note):
26869
+ * ptz.continuousMove({pan,tilt}) → navigation.move({pan,tilt})
26870
+ * ptz.stop() → navigation.stop()
26871
+ * ptz.goHome() → navigation.runAction('goHome')
26872
+ * ptz.getPresets() → navigation.listActions() (id→preset)
26873
+ * ptz.goToPreset(id) → navigation.runAction(id)
26874
+ *
26875
+ * ## Continuous drive
26876
+ *
26877
+ * `move` is MOMENTARY. Fluid navigation comes from the UI (or the PTZ adapter)
26878
+ * sending `move({pan,tilt})` REPEATEDLY at ~1 Hz while a direction is held, and
26879
+ * one `stop()` on release — exactly like the robot app's remote-drive joystick.
26880
+ * The provider forwards EACH `move` to one drive write; it must NOT debounce or
26881
+ * coalesce them. The UI owns the cadence.
26882
+ *
26883
+ * ## The action dictionary
26884
+ *
26885
+ * The discrete controls (dock / locate / spot-clean / follow / flash / sounds)
26886
+ * are a DATA-DRIVEN dictionary the cap exposes via `listActions()`. Each entry
26887
+ * carries `{ id, kind, label, icon }` (plus `soundId` for sound entries) so the
26888
+ * native panel AND the PTZ mimic render buttons WITHOUT hardcoding a
26889
+ * vendor-specific list. `kind: 'action'` entries are triggered with
26890
+ * `runAction({ actionId })`; `kind: 'sound'` entries with `playSound({ soundId })`
26891
+ * (the entry carries the `soundId` to pass). The general primitives — `move`,
26892
+ * `stop`, `goToPoint` — stay as first-class methods, not dictionary entries.
26893
+ *
26894
+ * NOTE (provider wiring): the first provider (Dreame) drives this with the RAW
26895
+ * `callAction(siid,aiid,in)` / `setProperty({siid,piid,value})` MIoT primitives
26896
+ * that the currently-published `@apocaliss92/nodedreame` already exposes on
26897
+ * every device handle. A future nodedreame publish adds a typed
26898
+ * `DreameCameraController` (drive / playPetSound / spotClean / findPet / …); the
26899
+ * provider can then swap the raw calls for the typed methods with no change to
26900
+ * THIS contract.
26901
+ */
26902
+ /**
26903
+ * A momentary drive nudge. The robot MOVES (no gimbal) for as long as the caller
26904
+ * keeps sending nudges (~1 Hz); an explicit `stop` (or letting the nudges lapse)
26905
+ * halts it.
26906
+ *
26907
+ * - `pan` — turn: negative = left, positive = right, 0 = straight.
26908
+ * - `tilt` — throttle: positive = forward, negative = spin / turn-around.
26909
+ * - `speed` — optional intensity hint [0, 1]; the provider may scale the drive
26910
+ * vector by it (drivers without proportional drive ignore it).
26911
+ *
26912
+ * `pan` / `tilt` are normalized [-1, 1]. Both optional so a caller can nudge one
26913
+ * axis alone; an all-undefined nudge is a no-op.
26914
+ */
26915
+ var NavigationMoveCommandSchema = object({
26916
+ pan: number().min(-1).max(1).optional(),
26917
+ tilt: number().min(-1).max(1).optional(),
26918
+ speed: number().min(0).max(1).optional()
26919
+ });
26920
+ /**
26921
+ * The enumerated discrete actions a navigation-capable robot can perform via
26922
+ * `runAction`. This is the CLOSED vocabulary; a given device advertises the
26923
+ * subset it supports through `listActions`. Sounds are NOT here — they go through
26924
+ * `playSound` (see the `sound` dictionary entries).
26925
+ */
26926
+ var NavigationActionIdSchema = _enum([
26927
+ "goHome",
26928
+ "locate",
26929
+ "spotClean",
26930
+ "findPet",
26931
+ "personFollow",
26932
+ "stop",
26933
+ "startClean",
26934
+ "pauseClean",
26935
+ "dockWash",
26936
+ "autoEmpty",
26937
+ "flashOn",
26938
+ "flashOff"
26939
+ ]);
26940
+ /** Whether a dictionary entry is a `runAction` action or a `playSound` sound. */
26941
+ var NavigationEntryKindSchema = _enum(["action", "sound"]);
26942
+ /**
26943
+ * One entry in the navigation action dictionary — the DATA-DRIVEN unit both the
26944
+ * native panel and the PTZ mimic render as a button.
26945
+ *
26946
+ * - `id` — stable id. For `kind:'action'` it is a {@link NavigationActionId}
26947
+ * (pass to `runAction`); for `kind:'sound'` it is a namespaced id
26948
+ * (`sound:meow`) whose `soundId` is passed to `playSound`.
26949
+ * - `icon` — icon HINT (lucide-style name; the UI maps it to its own set).
26950
+ * - `label` — operator-facing English label.
26951
+ * - `soundId` — wire sound id, present only on `kind:'sound'` entries.
26952
+ * - `enabled` — per-device FEATURE FLAG. `listActions` reports it so the UI /
26953
+ * PTZ render ONLY enabled entries. Data-driven: the provider
26954
+ * flips it from config, never by editing code.
26955
+ */
26956
+ var NavigationActionEntrySchema = object({
26957
+ id: string(),
26958
+ kind: NavigationEntryKindSchema,
26959
+ label: string(),
26960
+ icon: string(),
26961
+ /** Present only on `kind:'sound'` entries — the id to pass to `playSound`. */
26962
+ soundId: number().int().optional(),
26963
+ /** Per-device feature flag — render this entry only when true. */
26964
+ enabled: boolean()
26965
+ });
26966
+ /** Coordinates for `goToPoint` — a point on the robot's live map. */
26967
+ var NavigationPointSchema = object({
26968
+ x: number(),
26969
+ y: number()
26970
+ });
26971
+ /**
26972
+ * Per-device FEATURE-FLAG report for the general (non-dictionary) primitives.
26973
+ * The cap reports which are enabled so the UI / PTZ render only the controls
26974
+ * that are turned on for THIS device. Data-driven: the provider derives these
26975
+ * from config + probe, never hardcoded in the UI. The per-DICTIONARY-entry flags
26976
+ * live on {@link NavigationActionEntrySchema.enabled}; these gate the primitives
26977
+ * that are not dictionary entries.
26978
+ *
26979
+ * - `move` / `stop` — the momentary drive joystick.
26980
+ * - `goToPoint` — send-to-map-coordinate. Ships OFF on Dreame until the
26981
+ * map-coordinate plumbing is wired.
26982
+ * - `runAction` — the discrete action buttons (dictionary `kind:'action'`).
26983
+ * - `playSound` — the sound buttons (dictionary `kind:'sound'`).
26984
+ * - `light` — the on/off fill-light toggle (works anytime).
26985
+ * - `lightMode` — the auto/manual selector + manual level slider (a
26986
+ * camera-service control; needs an active stream).
26987
+ */
26988
+ var NavigationFeaturesSchema = object({
26989
+ move: boolean(),
26990
+ stop: boolean(),
26991
+ goToPoint: boolean(),
26992
+ runAction: boolean(),
26993
+ playSound: boolean(),
26994
+ light: boolean(),
26995
+ lightMode: boolean()
26996
+ });
26997
+ /** Light mode: `auto` lets the camera choose brightness; `manual` uses `level`. */
26998
+ var NavigationLightModeSchema = _enum(["auto", "manual"]);
26999
+ /**
27000
+ * Live navigation state so the UI can reflect what the robot is doing:
27001
+ * - `mode` — coarse activity (idle / cleaning / following / …).
27002
+ * - `following` — person/pet follow is currently armed.
27003
+ * - `flash` — the on-camera fill light is on.
27004
+ * - `lightMode` — auto vs manual fill-light mode.
27005
+ * - `lightLevel` — manual fill-light level (40..100); meaningful when
27006
+ * `lightMode === 'manual'`.
27007
+ */
27008
+ var NavigationStatusSchema = object({
27009
+ mode: _enum([
27010
+ "idle",
27011
+ "cleaning",
27012
+ "spot",
27013
+ "following",
27014
+ "goto",
27015
+ "returning",
27016
+ "paused",
27017
+ "unknown"
27018
+ ]),
27019
+ following: boolean(),
27020
+ flash: boolean(),
27021
+ lightMode: NavigationLightModeSchema,
27022
+ lightLevel: number().min(40).max(100),
27023
+ /** Ms epoch when the slice was last updated. */
27024
+ lastChangedAt: number()
27025
+ });
27026
+ NavigationStatusSchema.extend({ lastFetchedAt: number() });
27027
+ DeviceType.Camera, method(NavigationMoveCommandSchema.extend({ deviceId: number() }), _void(), { kind: "mutation" }), method(object({ deviceId: number() }), _void(), { kind: "mutation" }), method(NavigationPointSchema.extend({ deviceId: number() }), _void(), { kind: "mutation" }), method(object({ deviceId: number() }), array(NavigationActionEntrySchema)), method(object({
27028
+ deviceId: number(),
27029
+ actionId: NavigationActionIdSchema
27030
+ }), _void(), { kind: "mutation" }), method(object({
27031
+ deviceId: number(),
27032
+ soundId: number().int()
27033
+ }), _void(), { kind: "mutation" }), method(object({
27034
+ deviceId: number(),
27035
+ on: boolean()
27036
+ }), _void(), { kind: "mutation" }), method(object({
27037
+ deviceId: number(),
27038
+ mode: NavigationLightModeSchema,
27039
+ level: number().min(40).max(100).optional()
27040
+ }), _void(), { kind: "mutation" }), method(object({
27041
+ deviceId: number(),
27042
+ level: number().min(40).max(100)
27043
+ }), _void(), { kind: "mutation" }), method(object({ deviceId: number() }), NavigationFeaturesSchema), object({
27044
+ deviceId: number(),
27045
+ status: NavigationStatusSchema
27046
+ });
26810
27047
  DeviceType.Camera, DeviceType.Sensor, DeviceType.Switch, method(object({ deviceId: number().int().nonnegative() }), object({ success: literal(true) }), {
26811
27048
  kind: "mutation",
26812
27049
  auth: "admin"
@@ -31871,6 +32108,66 @@ Object.freeze({
31871
32108
  addonId: null,
31872
32109
  access: "create"
31873
32110
  },
32111
+ "navigation.getFeatures": {
32112
+ capName: "navigation",
32113
+ capScope: "device",
32114
+ addonId: null,
32115
+ access: "view"
32116
+ },
32117
+ "navigation.goToPoint": {
32118
+ capName: "navigation",
32119
+ capScope: "device",
32120
+ addonId: null,
32121
+ access: "create"
32122
+ },
32123
+ "navigation.listActions": {
32124
+ capName: "navigation",
32125
+ capScope: "device",
32126
+ addonId: null,
32127
+ access: "view"
32128
+ },
32129
+ "navigation.move": {
32130
+ capName: "navigation",
32131
+ capScope: "device",
32132
+ addonId: null,
32133
+ access: "create"
32134
+ },
32135
+ "navigation.playSound": {
32136
+ capName: "navigation",
32137
+ capScope: "device",
32138
+ addonId: null,
32139
+ access: "create"
32140
+ },
32141
+ "navigation.runAction": {
32142
+ capName: "navigation",
32143
+ capScope: "device",
32144
+ addonId: null,
32145
+ access: "create"
32146
+ },
32147
+ "navigation.setLightLevel": {
32148
+ capName: "navigation",
32149
+ capScope: "device",
32150
+ addonId: null,
32151
+ access: "create"
32152
+ },
32153
+ "navigation.setLightMode": {
32154
+ capName: "navigation",
32155
+ capScope: "device",
32156
+ addonId: null,
32157
+ access: "create"
32158
+ },
32159
+ "navigation.setLightOn": {
32160
+ capName: "navigation",
32161
+ capScope: "device",
32162
+ addonId: null,
32163
+ access: "create"
32164
+ },
32165
+ "navigation.stop": {
32166
+ capName: "navigation",
32167
+ capScope: "device",
32168
+ addonId: null,
32169
+ access: "create"
32170
+ },
31874
32171
  "networkAccess.getEndpoint": {
31875
32172
  capName: "network-access",
31876
32173
  capScope: "system",
@@ -35966,6 +36263,56 @@ Object.freeze({
35966
36263
  form: "single",
35967
36264
  optional: false
35968
36265
  }],
36266
+ "navigation.getFeatures": [{
36267
+ name: "deviceId",
36268
+ form: "single",
36269
+ optional: false
36270
+ }],
36271
+ "navigation.goToPoint": [{
36272
+ name: "deviceId",
36273
+ form: "single",
36274
+ optional: false
36275
+ }],
36276
+ "navigation.listActions": [{
36277
+ name: "deviceId",
36278
+ form: "single",
36279
+ optional: false
36280
+ }],
36281
+ "navigation.move": [{
36282
+ name: "deviceId",
36283
+ form: "single",
36284
+ optional: false
36285
+ }],
36286
+ "navigation.playSound": [{
36287
+ name: "deviceId",
36288
+ form: "single",
36289
+ optional: false
36290
+ }],
36291
+ "navigation.runAction": [{
36292
+ name: "deviceId",
36293
+ form: "single",
36294
+ optional: false
36295
+ }],
36296
+ "navigation.setLightLevel": [{
36297
+ name: "deviceId",
36298
+ form: "single",
36299
+ optional: false
36300
+ }],
36301
+ "navigation.setLightMode": [{
36302
+ name: "deviceId",
36303
+ form: "single",
36304
+ optional: false
36305
+ }],
36306
+ "navigation.setLightOn": [{
36307
+ name: "deviceId",
36308
+ form: "single",
36309
+ optional: false
36310
+ }],
36311
+ "navigation.stop": [{
36312
+ name: "deviceId",
36313
+ form: "single",
36314
+ optional: false
36315
+ }],
35969
36316
  "networkQuality.getDeviceStats": [{
35970
36317
  name: "deviceId",
35971
36318
  form: "single",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@camstack/addon-smtp-nodemailer",
3
- "version": "1.2.68",
3
+ "version": "1.2.70",
4
4
  "description": "SMTP email provider addon for CamStack — wraps `nodemailer` and registers a `smtp-provider` cap collection entry. Used by magic-link login + notifier addons.",
5
5
  "keywords": [
6
6
  "camstack",