@camstack/addon-remote-storage 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.
package/dist/s3.addon.js CHANGED
@@ -2,7 +2,7 @@ Object.defineProperties(exports, {
2
2
  __esModule: { value: true },
3
3
  [Symbol.toStringTag]: { value: "Module" }
4
4
  });
5
- const require_shared = require("./shared-DyZPICRX.js");
5
+ const require_shared = require("./shared-DiZPvkCY.js");
6
6
  let node_stream = require("node:stream");
7
7
  let _aws_sdk_client_s3 = require("@aws-sdk/client-s3");
8
8
  let _aws_sdk_lib_storage = require("@aws-sdk/lib-storage");
package/dist/s3.addon.mjs CHANGED
@@ -1,4 +1,4 @@
1
- import { c as BaseAddon, i as rearmIdleAbort, n as getOptionalBasePath, o as scheduleIdleAbort, s as storageProviderCapability, t as createSessionId } from "./shared-2Joc7r-U.mjs";
1
+ import { c as BaseAddon, i as rearmIdleAbort, n as getOptionalBasePath, o as scheduleIdleAbort, s as storageProviderCapability, t as createSessionId } from "./shared-Dg4Fpj1Q.mjs";
2
2
  import { PassThrough } from "node:stream";
3
3
  import { DeleteObjectCommand, GetObjectCommand, HeadBucketCommand, HeadObjectCommand, ListObjectsV2Command, PutObjectCommand, S3Client } from "@aws-sdk/client-s3";
4
4
  import { Upload } from "@aws-sdk/lib-storage";
@@ -2,7 +2,7 @@ Object.defineProperties(exports, {
2
2
  __esModule: { value: true },
3
3
  [Symbol.toStringTag]: { value: "Module" }
4
4
  });
5
- const require_shared = require("./shared-DyZPICRX.js");
5
+ const require_shared = require("./shared-DiZPvkCY.js");
6
6
  let ssh2 = require("ssh2");
7
7
  let node_path = require("node:path");
8
8
  node_path = require_shared.__toESM(node_path);
@@ -1,4 +1,4 @@
1
- import { a as safeJoinRemotePath, c as BaseAddon, i as rearmIdleAbort, o as scheduleIdleAbort, r as getRequiredBasePath, s as storageProviderCapability, t as createSessionId } from "./shared-2Joc7r-U.mjs";
1
+ import { a as safeJoinRemotePath, c as BaseAddon, i as rearmIdleAbort, o as scheduleIdleAbort, r as getRequiredBasePath, s as storageProviderCapability, t as createSessionId } from "./shared-Dg4Fpj1Q.mjs";
2
2
  import { Client } from "ssh2";
3
3
  import * as path from "node:path";
4
4
  //#region src/providers/sftp/sftp-config-schema.ts
@@ -1,6 +1,6 @@
1
1
  import { randomUUID } from "node:crypto";
2
2
  import * as path from "node:path";
3
- //#region ../types/dist/event-category-BZL-fdNj.mjs
3
+ //#region ../types/dist/event-category-zAv7pMUz.mjs
4
4
  var EventCategory = /* @__PURE__ */ function(EventCategory) {
5
5
  EventCategory["SystemBoot"] = "system.boot";
6
6
  EventCategory["SystemAddonsReady"] = "system.addons-ready";
@@ -372,6 +372,13 @@ var EventCategory = /* @__PURE__ */ function(EventCategory) {
372
372
  */
373
373
  EventCategory["BatteryOnStatusChanged"] = "battery.onStatusChanged";
374
374
  /**
375
+ * Cap event fired by every device that registers the `network-link`
376
+ * capability. Mirrors the cap definition's `onStatusChanged`. Carries
377
+ * `{ deviceId, status: NetworkLinkStatus }` — a link switch or a signal
378
+ * reading that moved.
379
+ */
380
+ EventCategory["NetworkLinkOnStatusChanged"] = "network-link.onStatusChanged";
381
+ /**
375
382
  * Emitted by the battery cap provider WHEN `wakeForStream` enters the
376
383
  * "wake in progress" window — between the Baichuan wake-up issue and
377
384
  * the camera's first dialed-back RTP packet. The stream-broker
@@ -23226,6 +23233,39 @@ DeviceType.Camera, DeviceType.Sensor, DeviceType.Button, DeviceType.Switch, meth
23226
23233
  deviceId: number(),
23227
23234
  status: BatteryStatusSchema
23228
23235
  });
23236
+ /**
23237
+ * Network-link snapshot. Same shape for every provider (a Reolink wifi
23238
+ * camera, a Home Assistant device with a signal-strength sensor, a Tapo
23239
+ * plug): one slice under `device.runtimeState['network-link']`, one badge,
23240
+ * one Home Assistant projection.
23241
+ */
23242
+ var NetworkLinkStatusSchema = object({
23243
+ /** The link the device is on. `'unknown'` = not read yet, not "no link". */
23244
+ type: _enum([
23245
+ "wifi",
23246
+ "ethernet",
23247
+ "cellular",
23248
+ "unknown"
23249
+ ]),
23250
+ /**
23251
+ * Link quality, 0..100 inclusive, normalised by the provider from whatever
23252
+ * the firmware reports (bars, RSSI, a vendor scale). **`null` means NOT
23253
+ * KNOWN or NOT APPLICABLE** — a wired link has no signal, and a wireless
23254
+ * one whose reading has not landed must not be drawn at 0 %. Consumers
23255
+ * SKIP a null rather than coerce it.
23256
+ */
23257
+ signalPercent: number().min(0).max(100).nullable(),
23258
+ /** Raw received signal strength in dBm, when the firmware reports one. */
23259
+ rssiDbm: number().optional(),
23260
+ /** Network name of a wireless link, when the firmware reports it. */
23261
+ ssid: string().optional(),
23262
+ /** Ms epoch of the last observation. Lets consumers reason about freshness. */
23263
+ lastUpdated: number()
23264
+ });
23265
+ DeviceType.Camera, DeviceType.Sensor, DeviceType.Button, DeviceType.Switch, DeviceType.Light, DeviceType.Lock, DeviceType.Siren, object({
23266
+ deviceId: number(),
23267
+ status: NetworkLinkStatusSchema
23268
+ });
23229
23269
  object({
23230
23270
  on: boolean(),
23231
23271
  /** Ms epoch of the last transition. 0 if never observed. */
@@ -26820,6 +26860,203 @@ DeviceType.Camera, method(object({ deviceId: number() }), PtzAutotrackStatusSche
26820
26860
  deviceId: number(),
26821
26861
  status: PtzAutotrackStatusSchema
26822
26862
  });
26863
+ /**
26864
+ * `navigation` — a device-scoped capability that natively expresses the FULL
26865
+ * navigation / action surface of a robot that DRIVES ITSELF and carries an
26866
+ * on-board camera (the Dreame robot-vacuum camera is the first provider).
26867
+ *
26868
+ * Why a NEW cap rather than overloading `ptz`:
26869
+ * - `ptz` moves a *gimbal* on a fixed camera (pan/tilt/zoom of the lens). A
26870
+ * robot vacuum has no gimbal — the whole chassis drives, turns and spins.
26871
+ * The two are different physical models: PTZ is absolute-position + presets,
26872
+ * navigation is momentary drive nudges + discrete robot ACTIONS
26873
+ * (dock / spot-clean / follow-pet / go-to-point / …).
26874
+ * - This cap is the SOURCE OF TRUTH. Two consumers adapt from it rather than
26875
+ * the reverse:
26876
+ * 1. a native CamStack navigation panel (data-driven from `listActions`
26877
+ * / `getOptions`), and
26878
+ * 2. the PTZ surface — a thin adapter mimics `ptz` from `navigation` so a
26879
+ * robot camera shows up in the existing PTZ control path without every
26880
+ * PTZ provider learning about robots. The mapping lives in the adapter,
26881
+ * not here (see the addon design note):
26882
+ * ptz.continuousMove({pan,tilt}) → navigation.move({pan,tilt})
26883
+ * ptz.stop() → navigation.stop()
26884
+ * ptz.goHome() → navigation.runAction('goHome')
26885
+ * ptz.getPresets() → navigation.listActions() (id→preset)
26886
+ * ptz.goToPreset(id) → navigation.runAction(id)
26887
+ *
26888
+ * ## Continuous drive
26889
+ *
26890
+ * `move` is MOMENTARY. Fluid navigation comes from the UI (or the PTZ adapter)
26891
+ * sending `move({pan,tilt})` REPEATEDLY at ~1 Hz while a direction is held, and
26892
+ * one `stop()` on release — exactly like the robot app's remote-drive joystick.
26893
+ * The provider forwards EACH `move` to one drive write; it must NOT debounce or
26894
+ * coalesce them. The UI owns the cadence.
26895
+ *
26896
+ * ## The action dictionary
26897
+ *
26898
+ * The discrete controls (dock / locate / spot-clean / follow / flash / sounds)
26899
+ * are a DATA-DRIVEN dictionary the cap exposes via `listActions()`. Each entry
26900
+ * carries `{ id, kind, label, icon }` (plus `soundId` for sound entries) so the
26901
+ * native panel AND the PTZ mimic render buttons WITHOUT hardcoding a
26902
+ * vendor-specific list. `kind: 'action'` entries are triggered with
26903
+ * `runAction({ actionId })`; `kind: 'sound'` entries with `playSound({ soundId })`
26904
+ * (the entry carries the `soundId` to pass). The general primitives — `move`,
26905
+ * `stop`, `goToPoint` — stay as first-class methods, not dictionary entries.
26906
+ *
26907
+ * NOTE (provider wiring): the first provider (Dreame) drives this with the RAW
26908
+ * `callAction(siid,aiid,in)` / `setProperty({siid,piid,value})` MIoT primitives
26909
+ * that the currently-published `@apocaliss92/nodedreame` already exposes on
26910
+ * every device handle. A future nodedreame publish adds a typed
26911
+ * `DreameCameraController` (drive / playPetSound / spotClean / findPet / …); the
26912
+ * provider can then swap the raw calls for the typed methods with no change to
26913
+ * THIS contract.
26914
+ */
26915
+ /**
26916
+ * A momentary drive nudge. The robot MOVES (no gimbal) for as long as the caller
26917
+ * keeps sending nudges (~1 Hz); an explicit `stop` (or letting the nudges lapse)
26918
+ * halts it.
26919
+ *
26920
+ * - `pan` — turn: negative = left, positive = right, 0 = straight.
26921
+ * - `tilt` — throttle: positive = forward, negative = spin / turn-around.
26922
+ * - `speed` — optional intensity hint [0, 1]; the provider may scale the drive
26923
+ * vector by it (drivers without proportional drive ignore it).
26924
+ *
26925
+ * `pan` / `tilt` are normalized [-1, 1]. Both optional so a caller can nudge one
26926
+ * axis alone; an all-undefined nudge is a no-op.
26927
+ */
26928
+ var NavigationMoveCommandSchema = object({
26929
+ pan: number().min(-1).max(1).optional(),
26930
+ tilt: number().min(-1).max(1).optional(),
26931
+ speed: number().min(0).max(1).optional()
26932
+ });
26933
+ /**
26934
+ * The enumerated discrete actions a navigation-capable robot can perform via
26935
+ * `runAction`. This is the CLOSED vocabulary; a given device advertises the
26936
+ * subset it supports through `listActions`. Sounds are NOT here — they go through
26937
+ * `playSound` (see the `sound` dictionary entries).
26938
+ */
26939
+ var NavigationActionIdSchema = _enum([
26940
+ "goHome",
26941
+ "locate",
26942
+ "spotClean",
26943
+ "findPet",
26944
+ "personFollow",
26945
+ "stop",
26946
+ "startClean",
26947
+ "pauseClean",
26948
+ "dockWash",
26949
+ "autoEmpty",
26950
+ "flashOn",
26951
+ "flashOff"
26952
+ ]);
26953
+ /** Whether a dictionary entry is a `runAction` action or a `playSound` sound. */
26954
+ var NavigationEntryKindSchema = _enum(["action", "sound"]);
26955
+ /**
26956
+ * One entry in the navigation action dictionary — the DATA-DRIVEN unit both the
26957
+ * native panel and the PTZ mimic render as a button.
26958
+ *
26959
+ * - `id` — stable id. For `kind:'action'` it is a {@link NavigationActionId}
26960
+ * (pass to `runAction`); for `kind:'sound'` it is a namespaced id
26961
+ * (`sound:meow`) whose `soundId` is passed to `playSound`.
26962
+ * - `icon` — icon HINT (lucide-style name; the UI maps it to its own set).
26963
+ * - `label` — operator-facing English label.
26964
+ * - `soundId` — wire sound id, present only on `kind:'sound'` entries.
26965
+ * - `enabled` — per-device FEATURE FLAG. `listActions` reports it so the UI /
26966
+ * PTZ render ONLY enabled entries. Data-driven: the provider
26967
+ * flips it from config, never by editing code.
26968
+ */
26969
+ var NavigationActionEntrySchema = object({
26970
+ id: string(),
26971
+ kind: NavigationEntryKindSchema,
26972
+ label: string(),
26973
+ icon: string(),
26974
+ /** Present only on `kind:'sound'` entries — the id to pass to `playSound`. */
26975
+ soundId: number().int().optional(),
26976
+ /** Per-device feature flag — render this entry only when true. */
26977
+ enabled: boolean()
26978
+ });
26979
+ /** Coordinates for `goToPoint` — a point on the robot's live map. */
26980
+ var NavigationPointSchema = object({
26981
+ x: number(),
26982
+ y: number()
26983
+ });
26984
+ /**
26985
+ * Per-device FEATURE-FLAG report for the general (non-dictionary) primitives.
26986
+ * The cap reports which are enabled so the UI / PTZ render only the controls
26987
+ * that are turned on for THIS device. Data-driven: the provider derives these
26988
+ * from config + probe, never hardcoded in the UI. The per-DICTIONARY-entry flags
26989
+ * live on {@link NavigationActionEntrySchema.enabled}; these gate the primitives
26990
+ * that are not dictionary entries.
26991
+ *
26992
+ * - `move` / `stop` — the momentary drive joystick.
26993
+ * - `goToPoint` — send-to-map-coordinate. Ships OFF on Dreame until the
26994
+ * map-coordinate plumbing is wired.
26995
+ * - `runAction` — the discrete action buttons (dictionary `kind:'action'`).
26996
+ * - `playSound` — the sound buttons (dictionary `kind:'sound'`).
26997
+ * - `light` — the on/off fill-light toggle (works anytime).
26998
+ * - `lightMode` — the auto/manual selector + manual level slider (a
26999
+ * camera-service control; needs an active stream).
27000
+ */
27001
+ var NavigationFeaturesSchema = object({
27002
+ move: boolean(),
27003
+ stop: boolean(),
27004
+ goToPoint: boolean(),
27005
+ runAction: boolean(),
27006
+ playSound: boolean(),
27007
+ light: boolean(),
27008
+ lightMode: boolean()
27009
+ });
27010
+ /** Light mode: `auto` lets the camera choose brightness; `manual` uses `level`. */
27011
+ var NavigationLightModeSchema = _enum(["auto", "manual"]);
27012
+ /**
27013
+ * Live navigation state so the UI can reflect what the robot is doing:
27014
+ * - `mode` — coarse activity (idle / cleaning / following / …).
27015
+ * - `following` — person/pet follow is currently armed.
27016
+ * - `flash` — the on-camera fill light is on.
27017
+ * - `lightMode` — auto vs manual fill-light mode.
27018
+ * - `lightLevel` — manual fill-light level (40..100); meaningful when
27019
+ * `lightMode === 'manual'`.
27020
+ */
27021
+ var NavigationStatusSchema = object({
27022
+ mode: _enum([
27023
+ "idle",
27024
+ "cleaning",
27025
+ "spot",
27026
+ "following",
27027
+ "goto",
27028
+ "returning",
27029
+ "paused",
27030
+ "unknown"
27031
+ ]),
27032
+ following: boolean(),
27033
+ flash: boolean(),
27034
+ lightMode: NavigationLightModeSchema,
27035
+ lightLevel: number().min(40).max(100),
27036
+ /** Ms epoch when the slice was last updated. */
27037
+ lastChangedAt: number()
27038
+ });
27039
+ NavigationStatusSchema.extend({ lastFetchedAt: number() });
27040
+ 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({
27041
+ deviceId: number(),
27042
+ actionId: NavigationActionIdSchema
27043
+ }), _void(), { kind: "mutation" }), method(object({
27044
+ deviceId: number(),
27045
+ soundId: number().int()
27046
+ }), _void(), { kind: "mutation" }), method(object({
27047
+ deviceId: number(),
27048
+ on: boolean()
27049
+ }), _void(), { kind: "mutation" }), method(object({
27050
+ deviceId: number(),
27051
+ mode: NavigationLightModeSchema,
27052
+ level: number().min(40).max(100).optional()
27053
+ }), _void(), { kind: "mutation" }), method(object({
27054
+ deviceId: number(),
27055
+ level: number().min(40).max(100)
27056
+ }), _void(), { kind: "mutation" }), method(object({ deviceId: number() }), NavigationFeaturesSchema), object({
27057
+ deviceId: number(),
27058
+ status: NavigationStatusSchema
27059
+ });
26823
27060
  DeviceType.Camera, DeviceType.Sensor, DeviceType.Switch, method(object({ deviceId: number().int().nonnegative() }), object({ success: literal(true) }), {
26824
27061
  kind: "mutation",
26825
27062
  auth: "admin"
@@ -31884,6 +32121,66 @@ Object.freeze({
31884
32121
  addonId: null,
31885
32122
  access: "create"
31886
32123
  },
32124
+ "navigation.getFeatures": {
32125
+ capName: "navigation",
32126
+ capScope: "device",
32127
+ addonId: null,
32128
+ access: "view"
32129
+ },
32130
+ "navigation.goToPoint": {
32131
+ capName: "navigation",
32132
+ capScope: "device",
32133
+ addonId: null,
32134
+ access: "create"
32135
+ },
32136
+ "navigation.listActions": {
32137
+ capName: "navigation",
32138
+ capScope: "device",
32139
+ addonId: null,
32140
+ access: "view"
32141
+ },
32142
+ "navigation.move": {
32143
+ capName: "navigation",
32144
+ capScope: "device",
32145
+ addonId: null,
32146
+ access: "create"
32147
+ },
32148
+ "navigation.playSound": {
32149
+ capName: "navigation",
32150
+ capScope: "device",
32151
+ addonId: null,
32152
+ access: "create"
32153
+ },
32154
+ "navigation.runAction": {
32155
+ capName: "navigation",
32156
+ capScope: "device",
32157
+ addonId: null,
32158
+ access: "create"
32159
+ },
32160
+ "navigation.setLightLevel": {
32161
+ capName: "navigation",
32162
+ capScope: "device",
32163
+ addonId: null,
32164
+ access: "create"
32165
+ },
32166
+ "navigation.setLightMode": {
32167
+ capName: "navigation",
32168
+ capScope: "device",
32169
+ addonId: null,
32170
+ access: "create"
32171
+ },
32172
+ "navigation.setLightOn": {
32173
+ capName: "navigation",
32174
+ capScope: "device",
32175
+ addonId: null,
32176
+ access: "create"
32177
+ },
32178
+ "navigation.stop": {
32179
+ capName: "navigation",
32180
+ capScope: "device",
32181
+ addonId: null,
32182
+ access: "create"
32183
+ },
31887
32184
  "networkAccess.getEndpoint": {
31888
32185
  capName: "network-access",
31889
32186
  capScope: "system",
@@ -35979,6 +36276,56 @@ Object.freeze({
35979
36276
  form: "single",
35980
36277
  optional: false
35981
36278
  }],
36279
+ "navigation.getFeatures": [{
36280
+ name: "deviceId",
36281
+ form: "single",
36282
+ optional: false
36283
+ }],
36284
+ "navigation.goToPoint": [{
36285
+ name: "deviceId",
36286
+ form: "single",
36287
+ optional: false
36288
+ }],
36289
+ "navigation.listActions": [{
36290
+ name: "deviceId",
36291
+ form: "single",
36292
+ optional: false
36293
+ }],
36294
+ "navigation.move": [{
36295
+ name: "deviceId",
36296
+ form: "single",
36297
+ optional: false
36298
+ }],
36299
+ "navigation.playSound": [{
36300
+ name: "deviceId",
36301
+ form: "single",
36302
+ optional: false
36303
+ }],
36304
+ "navigation.runAction": [{
36305
+ name: "deviceId",
36306
+ form: "single",
36307
+ optional: false
36308
+ }],
36309
+ "navigation.setLightLevel": [{
36310
+ name: "deviceId",
36311
+ form: "single",
36312
+ optional: false
36313
+ }],
36314
+ "navigation.setLightMode": [{
36315
+ name: "deviceId",
36316
+ form: "single",
36317
+ optional: false
36318
+ }],
36319
+ "navigation.setLightOn": [{
36320
+ name: "deviceId",
36321
+ form: "single",
36322
+ optional: false
36323
+ }],
36324
+ "navigation.stop": [{
36325
+ name: "deviceId",
36326
+ form: "single",
36327
+ optional: false
36328
+ }],
35982
36329
  "networkQuality.getDeviceStats": [{
35983
36330
  name: "deviceId",
35984
36331
  form: "single",
@@ -23,7 +23,7 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
23
23
  let node_crypto = require("node:crypto");
24
24
  let node_path = require("node:path");
25
25
  node_path = __toESM(node_path);
26
- //#region ../types/dist/event-category-BZL-fdNj.mjs
26
+ //#region ../types/dist/event-category-zAv7pMUz.mjs
27
27
  var EventCategory = /* @__PURE__ */ function(EventCategory) {
28
28
  EventCategory["SystemBoot"] = "system.boot";
29
29
  EventCategory["SystemAddonsReady"] = "system.addons-ready";
@@ -395,6 +395,13 @@ var EventCategory = /* @__PURE__ */ function(EventCategory) {
395
395
  */
396
396
  EventCategory["BatteryOnStatusChanged"] = "battery.onStatusChanged";
397
397
  /**
398
+ * Cap event fired by every device that registers the `network-link`
399
+ * capability. Mirrors the cap definition's `onStatusChanged`. Carries
400
+ * `{ deviceId, status: NetworkLinkStatus }` — a link switch or a signal
401
+ * reading that moved.
402
+ */
403
+ EventCategory["NetworkLinkOnStatusChanged"] = "network-link.onStatusChanged";
404
+ /**
398
405
  * Emitted by the battery cap provider WHEN `wakeForStream` enters the
399
406
  * "wake in progress" window — between the Baichuan wake-up issue and
400
407
  * the camera's first dialed-back RTP packet. The stream-broker
@@ -23249,6 +23256,39 @@ DeviceType.Camera, DeviceType.Sensor, DeviceType.Button, DeviceType.Switch, meth
23249
23256
  deviceId: number(),
23250
23257
  status: BatteryStatusSchema
23251
23258
  });
23259
+ /**
23260
+ * Network-link snapshot. Same shape for every provider (a Reolink wifi
23261
+ * camera, a Home Assistant device with a signal-strength sensor, a Tapo
23262
+ * plug): one slice under `device.runtimeState['network-link']`, one badge,
23263
+ * one Home Assistant projection.
23264
+ */
23265
+ var NetworkLinkStatusSchema = object({
23266
+ /** The link the device is on. `'unknown'` = not read yet, not "no link". */
23267
+ type: _enum([
23268
+ "wifi",
23269
+ "ethernet",
23270
+ "cellular",
23271
+ "unknown"
23272
+ ]),
23273
+ /**
23274
+ * Link quality, 0..100 inclusive, normalised by the provider from whatever
23275
+ * the firmware reports (bars, RSSI, a vendor scale). **`null` means NOT
23276
+ * KNOWN or NOT APPLICABLE** — a wired link has no signal, and a wireless
23277
+ * one whose reading has not landed must not be drawn at 0 %. Consumers
23278
+ * SKIP a null rather than coerce it.
23279
+ */
23280
+ signalPercent: number().min(0).max(100).nullable(),
23281
+ /** Raw received signal strength in dBm, when the firmware reports one. */
23282
+ rssiDbm: number().optional(),
23283
+ /** Network name of a wireless link, when the firmware reports it. */
23284
+ ssid: string().optional(),
23285
+ /** Ms epoch of the last observation. Lets consumers reason about freshness. */
23286
+ lastUpdated: number()
23287
+ });
23288
+ DeviceType.Camera, DeviceType.Sensor, DeviceType.Button, DeviceType.Switch, DeviceType.Light, DeviceType.Lock, DeviceType.Siren, object({
23289
+ deviceId: number(),
23290
+ status: NetworkLinkStatusSchema
23291
+ });
23252
23292
  object({
23253
23293
  on: boolean(),
23254
23294
  /** Ms epoch of the last transition. 0 if never observed. */
@@ -26843,6 +26883,203 @@ DeviceType.Camera, method(object({ deviceId: number() }), PtzAutotrackStatusSche
26843
26883
  deviceId: number(),
26844
26884
  status: PtzAutotrackStatusSchema
26845
26885
  });
26886
+ /**
26887
+ * `navigation` — a device-scoped capability that natively expresses the FULL
26888
+ * navigation / action surface of a robot that DRIVES ITSELF and carries an
26889
+ * on-board camera (the Dreame robot-vacuum camera is the first provider).
26890
+ *
26891
+ * Why a NEW cap rather than overloading `ptz`:
26892
+ * - `ptz` moves a *gimbal* on a fixed camera (pan/tilt/zoom of the lens). A
26893
+ * robot vacuum has no gimbal — the whole chassis drives, turns and spins.
26894
+ * The two are different physical models: PTZ is absolute-position + presets,
26895
+ * navigation is momentary drive nudges + discrete robot ACTIONS
26896
+ * (dock / spot-clean / follow-pet / go-to-point / …).
26897
+ * - This cap is the SOURCE OF TRUTH. Two consumers adapt from it rather than
26898
+ * the reverse:
26899
+ * 1. a native CamStack navigation panel (data-driven from `listActions`
26900
+ * / `getOptions`), and
26901
+ * 2. the PTZ surface — a thin adapter mimics `ptz` from `navigation` so a
26902
+ * robot camera shows up in the existing PTZ control path without every
26903
+ * PTZ provider learning about robots. The mapping lives in the adapter,
26904
+ * not here (see the addon design note):
26905
+ * ptz.continuousMove({pan,tilt}) → navigation.move({pan,tilt})
26906
+ * ptz.stop() → navigation.stop()
26907
+ * ptz.goHome() → navigation.runAction('goHome')
26908
+ * ptz.getPresets() → navigation.listActions() (id→preset)
26909
+ * ptz.goToPreset(id) → navigation.runAction(id)
26910
+ *
26911
+ * ## Continuous drive
26912
+ *
26913
+ * `move` is MOMENTARY. Fluid navigation comes from the UI (or the PTZ adapter)
26914
+ * sending `move({pan,tilt})` REPEATEDLY at ~1 Hz while a direction is held, and
26915
+ * one `stop()` on release — exactly like the robot app's remote-drive joystick.
26916
+ * The provider forwards EACH `move` to one drive write; it must NOT debounce or
26917
+ * coalesce them. The UI owns the cadence.
26918
+ *
26919
+ * ## The action dictionary
26920
+ *
26921
+ * The discrete controls (dock / locate / spot-clean / follow / flash / sounds)
26922
+ * are a DATA-DRIVEN dictionary the cap exposes via `listActions()`. Each entry
26923
+ * carries `{ id, kind, label, icon }` (plus `soundId` for sound entries) so the
26924
+ * native panel AND the PTZ mimic render buttons WITHOUT hardcoding a
26925
+ * vendor-specific list. `kind: 'action'` entries are triggered with
26926
+ * `runAction({ actionId })`; `kind: 'sound'` entries with `playSound({ soundId })`
26927
+ * (the entry carries the `soundId` to pass). The general primitives — `move`,
26928
+ * `stop`, `goToPoint` — stay as first-class methods, not dictionary entries.
26929
+ *
26930
+ * NOTE (provider wiring): the first provider (Dreame) drives this with the RAW
26931
+ * `callAction(siid,aiid,in)` / `setProperty({siid,piid,value})` MIoT primitives
26932
+ * that the currently-published `@apocaliss92/nodedreame` already exposes on
26933
+ * every device handle. A future nodedreame publish adds a typed
26934
+ * `DreameCameraController` (drive / playPetSound / spotClean / findPet / …); the
26935
+ * provider can then swap the raw calls for the typed methods with no change to
26936
+ * THIS contract.
26937
+ */
26938
+ /**
26939
+ * A momentary drive nudge. The robot MOVES (no gimbal) for as long as the caller
26940
+ * keeps sending nudges (~1 Hz); an explicit `stop` (or letting the nudges lapse)
26941
+ * halts it.
26942
+ *
26943
+ * - `pan` — turn: negative = left, positive = right, 0 = straight.
26944
+ * - `tilt` — throttle: positive = forward, negative = spin / turn-around.
26945
+ * - `speed` — optional intensity hint [0, 1]; the provider may scale the drive
26946
+ * vector by it (drivers without proportional drive ignore it).
26947
+ *
26948
+ * `pan` / `tilt` are normalized [-1, 1]. Both optional so a caller can nudge one
26949
+ * axis alone; an all-undefined nudge is a no-op.
26950
+ */
26951
+ var NavigationMoveCommandSchema = object({
26952
+ pan: number().min(-1).max(1).optional(),
26953
+ tilt: number().min(-1).max(1).optional(),
26954
+ speed: number().min(0).max(1).optional()
26955
+ });
26956
+ /**
26957
+ * The enumerated discrete actions a navigation-capable robot can perform via
26958
+ * `runAction`. This is the CLOSED vocabulary; a given device advertises the
26959
+ * subset it supports through `listActions`. Sounds are NOT here — they go through
26960
+ * `playSound` (see the `sound` dictionary entries).
26961
+ */
26962
+ var NavigationActionIdSchema = _enum([
26963
+ "goHome",
26964
+ "locate",
26965
+ "spotClean",
26966
+ "findPet",
26967
+ "personFollow",
26968
+ "stop",
26969
+ "startClean",
26970
+ "pauseClean",
26971
+ "dockWash",
26972
+ "autoEmpty",
26973
+ "flashOn",
26974
+ "flashOff"
26975
+ ]);
26976
+ /** Whether a dictionary entry is a `runAction` action or a `playSound` sound. */
26977
+ var NavigationEntryKindSchema = _enum(["action", "sound"]);
26978
+ /**
26979
+ * One entry in the navigation action dictionary — the DATA-DRIVEN unit both the
26980
+ * native panel and the PTZ mimic render as a button.
26981
+ *
26982
+ * - `id` — stable id. For `kind:'action'` it is a {@link NavigationActionId}
26983
+ * (pass to `runAction`); for `kind:'sound'` it is a namespaced id
26984
+ * (`sound:meow`) whose `soundId` is passed to `playSound`.
26985
+ * - `icon` — icon HINT (lucide-style name; the UI maps it to its own set).
26986
+ * - `label` — operator-facing English label.
26987
+ * - `soundId` — wire sound id, present only on `kind:'sound'` entries.
26988
+ * - `enabled` — per-device FEATURE FLAG. `listActions` reports it so the UI /
26989
+ * PTZ render ONLY enabled entries. Data-driven: the provider
26990
+ * flips it from config, never by editing code.
26991
+ */
26992
+ var NavigationActionEntrySchema = object({
26993
+ id: string(),
26994
+ kind: NavigationEntryKindSchema,
26995
+ label: string(),
26996
+ icon: string(),
26997
+ /** Present only on `kind:'sound'` entries — the id to pass to `playSound`. */
26998
+ soundId: number().int().optional(),
26999
+ /** Per-device feature flag — render this entry only when true. */
27000
+ enabled: boolean()
27001
+ });
27002
+ /** Coordinates for `goToPoint` — a point on the robot's live map. */
27003
+ var NavigationPointSchema = object({
27004
+ x: number(),
27005
+ y: number()
27006
+ });
27007
+ /**
27008
+ * Per-device FEATURE-FLAG report for the general (non-dictionary) primitives.
27009
+ * The cap reports which are enabled so the UI / PTZ render only the controls
27010
+ * that are turned on for THIS device. Data-driven: the provider derives these
27011
+ * from config + probe, never hardcoded in the UI. The per-DICTIONARY-entry flags
27012
+ * live on {@link NavigationActionEntrySchema.enabled}; these gate the primitives
27013
+ * that are not dictionary entries.
27014
+ *
27015
+ * - `move` / `stop` — the momentary drive joystick.
27016
+ * - `goToPoint` — send-to-map-coordinate. Ships OFF on Dreame until the
27017
+ * map-coordinate plumbing is wired.
27018
+ * - `runAction` — the discrete action buttons (dictionary `kind:'action'`).
27019
+ * - `playSound` — the sound buttons (dictionary `kind:'sound'`).
27020
+ * - `light` — the on/off fill-light toggle (works anytime).
27021
+ * - `lightMode` — the auto/manual selector + manual level slider (a
27022
+ * camera-service control; needs an active stream).
27023
+ */
27024
+ var NavigationFeaturesSchema = object({
27025
+ move: boolean(),
27026
+ stop: boolean(),
27027
+ goToPoint: boolean(),
27028
+ runAction: boolean(),
27029
+ playSound: boolean(),
27030
+ light: boolean(),
27031
+ lightMode: boolean()
27032
+ });
27033
+ /** Light mode: `auto` lets the camera choose brightness; `manual` uses `level`. */
27034
+ var NavigationLightModeSchema = _enum(["auto", "manual"]);
27035
+ /**
27036
+ * Live navigation state so the UI can reflect what the robot is doing:
27037
+ * - `mode` — coarse activity (idle / cleaning / following / …).
27038
+ * - `following` — person/pet follow is currently armed.
27039
+ * - `flash` — the on-camera fill light is on.
27040
+ * - `lightMode` — auto vs manual fill-light mode.
27041
+ * - `lightLevel` — manual fill-light level (40..100); meaningful when
27042
+ * `lightMode === 'manual'`.
27043
+ */
27044
+ var NavigationStatusSchema = object({
27045
+ mode: _enum([
27046
+ "idle",
27047
+ "cleaning",
27048
+ "spot",
27049
+ "following",
27050
+ "goto",
27051
+ "returning",
27052
+ "paused",
27053
+ "unknown"
27054
+ ]),
27055
+ following: boolean(),
27056
+ flash: boolean(),
27057
+ lightMode: NavigationLightModeSchema,
27058
+ lightLevel: number().min(40).max(100),
27059
+ /** Ms epoch when the slice was last updated. */
27060
+ lastChangedAt: number()
27061
+ });
27062
+ NavigationStatusSchema.extend({ lastFetchedAt: number() });
27063
+ 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({
27064
+ deviceId: number(),
27065
+ actionId: NavigationActionIdSchema
27066
+ }), _void(), { kind: "mutation" }), method(object({
27067
+ deviceId: number(),
27068
+ soundId: number().int()
27069
+ }), _void(), { kind: "mutation" }), method(object({
27070
+ deviceId: number(),
27071
+ on: boolean()
27072
+ }), _void(), { kind: "mutation" }), method(object({
27073
+ deviceId: number(),
27074
+ mode: NavigationLightModeSchema,
27075
+ level: number().min(40).max(100).optional()
27076
+ }), _void(), { kind: "mutation" }), method(object({
27077
+ deviceId: number(),
27078
+ level: number().min(40).max(100)
27079
+ }), _void(), { kind: "mutation" }), method(object({ deviceId: number() }), NavigationFeaturesSchema), object({
27080
+ deviceId: number(),
27081
+ status: NavigationStatusSchema
27082
+ });
26846
27083
  DeviceType.Camera, DeviceType.Sensor, DeviceType.Switch, method(object({ deviceId: number().int().nonnegative() }), object({ success: literal(true) }), {
26847
27084
  kind: "mutation",
26848
27085
  auth: "admin"
@@ -31907,6 +32144,66 @@ Object.freeze({
31907
32144
  addonId: null,
31908
32145
  access: "create"
31909
32146
  },
32147
+ "navigation.getFeatures": {
32148
+ capName: "navigation",
32149
+ capScope: "device",
32150
+ addonId: null,
32151
+ access: "view"
32152
+ },
32153
+ "navigation.goToPoint": {
32154
+ capName: "navigation",
32155
+ capScope: "device",
32156
+ addonId: null,
32157
+ access: "create"
32158
+ },
32159
+ "navigation.listActions": {
32160
+ capName: "navigation",
32161
+ capScope: "device",
32162
+ addonId: null,
32163
+ access: "view"
32164
+ },
32165
+ "navigation.move": {
32166
+ capName: "navigation",
32167
+ capScope: "device",
32168
+ addonId: null,
32169
+ access: "create"
32170
+ },
32171
+ "navigation.playSound": {
32172
+ capName: "navigation",
32173
+ capScope: "device",
32174
+ addonId: null,
32175
+ access: "create"
32176
+ },
32177
+ "navigation.runAction": {
32178
+ capName: "navigation",
32179
+ capScope: "device",
32180
+ addonId: null,
32181
+ access: "create"
32182
+ },
32183
+ "navigation.setLightLevel": {
32184
+ capName: "navigation",
32185
+ capScope: "device",
32186
+ addonId: null,
32187
+ access: "create"
32188
+ },
32189
+ "navigation.setLightMode": {
32190
+ capName: "navigation",
32191
+ capScope: "device",
32192
+ addonId: null,
32193
+ access: "create"
32194
+ },
32195
+ "navigation.setLightOn": {
32196
+ capName: "navigation",
32197
+ capScope: "device",
32198
+ addonId: null,
32199
+ access: "create"
32200
+ },
32201
+ "navigation.stop": {
32202
+ capName: "navigation",
32203
+ capScope: "device",
32204
+ addonId: null,
32205
+ access: "create"
32206
+ },
31910
32207
  "networkAccess.getEndpoint": {
31911
32208
  capName: "network-access",
31912
32209
  capScope: "system",
@@ -36002,6 +36299,56 @@ Object.freeze({
36002
36299
  form: "single",
36003
36300
  optional: false
36004
36301
  }],
36302
+ "navigation.getFeatures": [{
36303
+ name: "deviceId",
36304
+ form: "single",
36305
+ optional: false
36306
+ }],
36307
+ "navigation.goToPoint": [{
36308
+ name: "deviceId",
36309
+ form: "single",
36310
+ optional: false
36311
+ }],
36312
+ "navigation.listActions": [{
36313
+ name: "deviceId",
36314
+ form: "single",
36315
+ optional: false
36316
+ }],
36317
+ "navigation.move": [{
36318
+ name: "deviceId",
36319
+ form: "single",
36320
+ optional: false
36321
+ }],
36322
+ "navigation.playSound": [{
36323
+ name: "deviceId",
36324
+ form: "single",
36325
+ optional: false
36326
+ }],
36327
+ "navigation.runAction": [{
36328
+ name: "deviceId",
36329
+ form: "single",
36330
+ optional: false
36331
+ }],
36332
+ "navigation.setLightLevel": [{
36333
+ name: "deviceId",
36334
+ form: "single",
36335
+ optional: false
36336
+ }],
36337
+ "navigation.setLightMode": [{
36338
+ name: "deviceId",
36339
+ form: "single",
36340
+ optional: false
36341
+ }],
36342
+ "navigation.setLightOn": [{
36343
+ name: "deviceId",
36344
+ form: "single",
36345
+ optional: false
36346
+ }],
36347
+ "navigation.stop": [{
36348
+ name: "deviceId",
36349
+ form: "single",
36350
+ optional: false
36351
+ }],
36005
36352
  "networkQuality.getDeviceStats": [{
36006
36353
  name: "deviceId",
36007
36354
  form: "single",
package/dist/smb.addon.js CHANGED
@@ -2,7 +2,7 @@ Object.defineProperties(exports, {
2
2
  __esModule: { value: true },
3
3
  [Symbol.toStringTag]: { value: "Module" }
4
4
  });
5
- const require_shared = require("./shared-DyZPICRX.js");
5
+ const require_shared = require("./shared-DiZPvkCY.js");
6
6
  let node_crypto = require("node:crypto");
7
7
  let node_path = require("node:path");
8
8
  node_path = require_shared.__toESM(node_path);
@@ -1,4 +1,4 @@
1
- import { c as BaseAddon, i as rearmIdleAbort, o as scheduleIdleAbort, s as storageProviderCapability } from "./shared-2Joc7r-U.mjs";
1
+ import { c as BaseAddon, i as rearmIdleAbort, o as scheduleIdleAbort, s as storageProviderCapability } from "./shared-Dg4Fpj1Q.mjs";
2
2
  import { randomUUID } from "node:crypto";
3
3
  import * as path from "node:path";
4
4
  import * as fsp from "node:fs/promises";
@@ -2,7 +2,7 @@ Object.defineProperties(exports, {
2
2
  __esModule: { value: true },
3
3
  [Symbol.toStringTag]: { value: "Module" }
4
4
  });
5
- const require_shared = require("./shared-DyZPICRX.js");
5
+ const require_shared = require("./shared-DiZPvkCY.js");
6
6
  let node_stream = require("node:stream");
7
7
  let webdav = require("webdav");
8
8
  //#region src/providers/webdav/webdav-config-schema.ts
@@ -1,4 +1,4 @@
1
- import { a as safeJoinRemotePath, c as BaseAddon, i as rearmIdleAbort, o as scheduleIdleAbort, r as getRequiredBasePath, s as storageProviderCapability, t as createSessionId } from "./shared-2Joc7r-U.mjs";
1
+ import { a as safeJoinRemotePath, c as BaseAddon, i as rearmIdleAbort, o as scheduleIdleAbort, r as getRequiredBasePath, s as storageProviderCapability, t as createSessionId } from "./shared-Dg4Fpj1Q.mjs";
2
2
  import { PassThrough } from "node:stream";
3
3
  import { AuthType, createClient } from "webdav";
4
4
  //#region src/providers/webdav/webdav-config-schema.ts
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@camstack/addon-remote-storage",
3
- "version": "1.2.68",
3
+ "version": "1.2.70",
4
4
  "description": "Remote storage providers (SFTP, S3, WebDAV, SMB) — unifies remote backends behind the storage-provider cap",
5
5
  "keywords": [
6
6
  "camstack",