@camstack/addon-provider-gree 0.2.96 → 0.2.97

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/dist/addon.js +151 -22
  2. package/dist/addon.mjs +151 -22
  3. package/package.json +1 -1
package/dist/addon.js CHANGED
@@ -7306,6 +7306,23 @@ function errMsg(err) {
7306
7306
  if (typeof err === "string") return err;
7307
7307
  return String(err);
7308
7308
  }
7309
+ new Set([
7310
+ "track",
7311
+ "summary",
7312
+ "face",
7313
+ "identity",
7314
+ "plate",
7315
+ "vehicle",
7316
+ "scene",
7317
+ "motion",
7318
+ "object",
7319
+ "audio"
7320
+ ]);
7321
+ new Set([
7322
+ "motion",
7323
+ "object",
7324
+ "audio"
7325
+ ]);
7309
7326
  var EncodeProfileSchema = object({
7310
7327
  video: object({
7311
7328
  codec: _enum([
@@ -12506,8 +12523,17 @@ method(object({
12506
12523
  }), array(SettingsRecordSchema).readonly()), method(object({
12507
12524
  namespace: string().optional(),
12508
12525
  collection: string(),
12509
- record: SettingsRecordSchema
12510
- }), _void(), { kind: "mutation" }), method(object({
12526
+ record: object({
12527
+ id: string().optional(),
12528
+ data: record(string(), unknown())
12529
+ })
12530
+ }), object({
12531
+ /**
12532
+ * The id the row ACTUALLY got (D473): the one supplied, the UUID minted
12533
+ * for an absent one, or the ROWID SQLite assigned on an `INTEGER`
12534
+ * primary key — which is the only place an auto key is knowable.
12535
+ */
12536
+ id: union([string(), number()]) }), { kind: "mutation" }), method(object({
12511
12537
  namespace: string().optional(),
12512
12538
  collection: string(),
12513
12539
  records: array(BulkRecordSchema).readonly()
@@ -12616,8 +12642,17 @@ method(_void(), EngineInfoSchema, { auth: "admin" }), method(object({
12616
12642
  }), array(SettingsRecordSchema).readonly(), { auth: "admin" }), method(object({
12617
12643
  namespace: string().optional(),
12618
12644
  collection: string(),
12619
- record: SettingsRecordSchema
12620
- }), _void(), {
12645
+ record: object({
12646
+ id: string().optional(),
12647
+ data: record(string(), unknown())
12648
+ })
12649
+ }), object({
12650
+ /**
12651
+ * The id the row ACTUALLY got (D473): the one supplied, the UUID minted
12652
+ * for an absent one, or the ROWID SQLite assigned on an `INTEGER`
12653
+ * primary key — which is the only place an auto key is knowable.
12654
+ */
12655
+ id: union([string(), number()]) }), {
12621
12656
  kind: "mutation",
12622
12657
  auth: "admin"
12623
12658
  }), method(object({
@@ -19525,7 +19560,20 @@ var TrackSchema = object({
19525
19560
  ...TrackRetrainFields
19526
19561
  });
19527
19562
  var BaseEventFields = {
19528
- id: string(),
19563
+ /**
19564
+ * A SQLite ROWID, assigned by the database (D474).
19565
+ *
19566
+ * Was a 36-character UUID and cost 263 MB of a 1 117 MB database — paid
19567
+ * TWICE per row, in the row and in the primary-key index, across 2.1 million
19568
+ * motion, audio and object events. An `INTEGER PRIMARY KEY` in SQLite **is**
19569
+ * the rowid: the table itself is that B-tree, so the index stops existing
19570
+ * rather than getting smaller. No shorter string does that.
19571
+ *
19572
+ * Defined once here for all three event kinds, which is why they move
19573
+ * together: a per-table migration would have forked this and
19574
+ * `COMMON_BASE_COLUMNS` and reunited them two stages later.
19575
+ */
19576
+ id: number().int(),
19529
19577
  deviceId: number(),
19530
19578
  timestamp: number()
19531
19579
  };
@@ -19544,7 +19592,34 @@ var MotionEventSchema = object({
19544
19592
  /** Omitted in slim projection. */
19545
19593
  frameHeight: number().optional(),
19546
19594
  /** Populated by B5 (recording playback URL for this event). */
19547
- mediaUrl: string().optional()
19595
+ mediaUrl: string().optional(),
19596
+ /**
19597
+ * One row per motion EPISODE, not one per push (D475). `null` while the
19598
+ * episode is still open — a further rising edge extends it in place rather
19599
+ * than inserting a new row. Set once, at close, to `lastOnAt - startedAt`
19600
+ * (the span from the first rising edge to the LAST one, deliberately NOT
19601
+ * `closedAt - startedAt` — the close delay is a quiet CONFIRMATION, not
19602
+ * movement, and folding it in would report `MOTION_CLOSE_AFTER_MS` of
19603
+ * motion for an instantaneous trigger).
19604
+ *
19605
+ * **Absent** (not merely `null`) on a row written before D475 — that means
19606
+ * "closed the old way, before this column existed", never "still open".
19607
+ * Nothing in this codebase may read an absent `durationMs` as an open
19608
+ * episode; only `null` means open.
19609
+ */
19610
+ durationMs: number().nullable().optional(),
19611
+ /**
19612
+ * Ms offsets from `timestamp` (the episode's own first rising edge, so the
19613
+ * first entry is always `0`) of every genuine off→on transition the
19614
+ * episode saw — "ogni evento on si deve salvare" (D475). NOT one entry per
19615
+ * push: a firmware source that keepalives at ~1 Hz for the whole burst
19616
+ * (Reolink, Hikvision) produces exactly one edge; a source that reports an
19617
+ * explicit `false` mid-episode and then resumes before the quiet window
19618
+ * elapses produces another. Stored compactly — see `motion-edge-codec.ts`
19619
+ * — and decoded back to this shape on read. Absent/empty on a legacy row,
19620
+ * which must never be read as "no episode happened here".
19621
+ */
19622
+ edges: array(number()).readonly().optional()
19548
19623
  });
19549
19624
  /**
19550
19625
  * Which detection SOURCE produced an object event. `pipeline` = the ML
@@ -19599,6 +19674,23 @@ var ObjectEventSchema = object({
19599
19674
  * includes it (it is light). Absent on rows written before this field.
19600
19675
  */
19601
19676
  frameId: string().optional(),
19677
+ /**
19678
+ * A PRODUCER-chosen key that makes a synthetic event's emission idempotent
19679
+ * (D474).
19680
+ *
19681
+ * Only the package detector writes it, and it exists because the event id
19682
+ * stopped being choosable: the delivery and pick-up rows used to BE their
19683
+ * dedupe key (`pa-pkg-<entryId>-delivered`), which is how "never emit a
19684
+ * second delivery for this entry" survived a restart. An `INTEGER` rowid is
19685
+ * assigned by SQLite, so that key had to move off the primary key rather
19686
+ * than be dropped — a detector that cannot recognise its own row re-delivers
19687
+ * every parcel on every boot.
19688
+ *
19689
+ * Absent on every other object event, and on every row written before this
19690
+ * field. Never a substitute for `id`: it is unique per (producer, occasion),
19691
+ * not per row, and nothing addresses a row by it.
19692
+ */
19693
+ idempotencyKey: string().optional(),
19602
19694
  /** Omitted in slim projection. */
19603
19695
  trackId: string().optional(),
19604
19696
  className: string(),
@@ -27848,26 +27940,59 @@ authKey: string().optional() }), object({
27848
27940
  /** Human-readable error when `ok: false`. */
27849
27941
  error: string().optional()
27850
27942
  }), { kind: "mutation" });
27851
- /**
27852
- * Hardware / firmware motion sensor cap — binary detected state plus
27853
- * a timestamp of the last observation. Distinct from
27854
- * `motion-detection.cap.ts` which owns the LOCAL ML motion pipeline;
27855
- * `motion` is the lightweight readout from on-camera motion (Reolink
27856
- * `GetMdState`, Baichuan push `type: motion`, ONVIF analytics).
27857
- *
27858
- * Native-motion providers also fan out to `detection.camera-native`
27859
- * with `source: 'onboard'` so cross-cutting system services
27860
- * (alert-center, advanced-notifier) can subscribe once and receive
27861
- * motion from every camera.
27862
- */
27863
27943
  var MotionStatusSchema = object({
27864
27944
  detected: boolean(),
27865
27945
  /** Ms epoch of the last detected-true observation. Null if never detected. */
27866
27946
  lastDetectedAt: number().nullable(),
27867
27947
  /**
27868
- * Ms after which `detected` auto-reverts to false if no fresh push
27869
- * arrives. Null means the provider leaves detected state until a
27870
- * native "clear" event.
27948
+ * `MOTION_CLOSE_AFTER_MS` while `detected: true` on a `Camera` device,
27949
+ * `null` while false and on every `Sensor` device (D475) — see that
27950
+ * constant's doc for the one-authority rule.
27951
+ *
27952
+ * ## Reading this field still arms nothing
27953
+ *
27954
+ * It reads like an instruction to the consumer ("revert after N ms if
27955
+ * no fresh push arrives") and it is not one: nothing in this repo reads
27956
+ * the LIVE cap value to drive a timer. `pipeline-analytics`'s motion-episode
27957
+ * close DOES now use the same number — `MOTION_CLOSE_AFTER_MS` — but as an
27958
+ * imported constant, not as a read of `device.state.motion.value`, so this
27959
+ * field stays what it always was: DESCRIPTIVE output, mirroring an answer
27960
+ * computed elsewhere. Building a self-clear timer out of a READ of this
27961
+ * field would add a second falling-edge authority beside whichever one
27962
+ * already owns the device, and two that can disagree are worse than one.
27963
+ * Consumers that need a falling edge SHAPED differently — held open across
27964
+ * a flapping source — debounce on their own side and say so, as
27965
+ * `addon-export-alexa/src/motion-clear-hold.ts` and
27966
+ * `addon-export-hap`'s `RESET_DEBOUNCE_MS` both do.
27967
+ *
27968
+ * ## Who writes it
27969
+ *
27970
+ * - **Cameras** — the runner's phase machine, `active → watching` on
27971
+ * `cooldown_expired`, which then writes this slice with
27972
+ * `detected: false` (`handlePhaseChanged` in
27973
+ * `pipeline-runner/index.ts`). It produces the FALLING edge, which
27974
+ * matters most for the sources that only ever push a rising one:
27975
+ * Reolink emits `MotionOnMotionChanged { detected: true }` and never
27976
+ * a false.
27977
+ * - **Sensors** (Home Assistant binary sensors, Homematic) — the
27978
+ * provider pushes the false itself, from the upstream system's own
27979
+ * state change. No phase machine is involved.
27980
+ *
27981
+ * ### The phase machine is CANONICAL, not sole — and that is a defect
27982
+ *
27983
+ * An earlier revision of this docblock (mine, 2026-09-12) claimed the
27984
+ * phase machine is the sole writer for a camera. It is not.
27985
+ * `hikvision-camera.ts:3464` and `amcrest-camera.ts:445` both call
27986
+ * `setCapSlice(motionCapability, …)` on their own rising edge, and
27987
+ * Hikvision's comment says why: it read THIS docblock, agreed the
27988
+ * runner is canonical, and wrote anyway to avoid per-tick churn. So
27989
+ * two authorities can disagree about one slice, which this repo
27990
+ * forbids, and the doc said otherwise — which is worse than saying
27991
+ * nothing, because it reads as verification.
27992
+ *
27993
+ * This predates D475 and is not fixed there: the fix touches every
27994
+ * camera provider. Recorded in D475's Consequences. Do not restore the
27995
+ * "sole writer" wording without also removing the other writers.
27871
27996
  */
27872
27997
  autoClearAfterMs: number().nullable()
27873
27998
  });
@@ -27937,7 +28062,11 @@ onMotionChanged: { data: MotionOnMotionChangedDataSchema } },
27937
28062
  */
27938
28063
  runtimeState: MotionStatusSchema,
27939
28064
  /**
27940
- * Runtime-state durability: **session** — self-clearing by construction (`autoClearAfterMs`); a restored `detected: true` is a frozen event, and the next frame re-publishes the real one.
28065
+ * Runtime-state durability: **session** — every writer of this slice
28066
+ * writes only on an EDGE, so a restored `detected: true` would stay
28067
+ * frozen until the next one instead of being corrected. The next edge
28068
+ * re-publishes the real state. (On who the writers are, and why there
28069
+ * is more than one, see `autoClearAfterMs` above.)
27941
28070
  *
27942
28071
  * See `RuntimeStateDurability`. Enforced by
27943
28072
  * `scripts/check-runtime-state-durability.ts`.
package/dist/addon.mjs CHANGED
@@ -7305,6 +7305,23 @@ function errMsg(err) {
7305
7305
  if (typeof err === "string") return err;
7306
7306
  return String(err);
7307
7307
  }
7308
+ new Set([
7309
+ "track",
7310
+ "summary",
7311
+ "face",
7312
+ "identity",
7313
+ "plate",
7314
+ "vehicle",
7315
+ "scene",
7316
+ "motion",
7317
+ "object",
7318
+ "audio"
7319
+ ]);
7320
+ new Set([
7321
+ "motion",
7322
+ "object",
7323
+ "audio"
7324
+ ]);
7308
7325
  var EncodeProfileSchema = object({
7309
7326
  video: object({
7310
7327
  codec: _enum([
@@ -12505,8 +12522,17 @@ method(object({
12505
12522
  }), array(SettingsRecordSchema).readonly()), method(object({
12506
12523
  namespace: string().optional(),
12507
12524
  collection: string(),
12508
- record: SettingsRecordSchema
12509
- }), _void(), { kind: "mutation" }), method(object({
12525
+ record: object({
12526
+ id: string().optional(),
12527
+ data: record(string(), unknown())
12528
+ })
12529
+ }), object({
12530
+ /**
12531
+ * The id the row ACTUALLY got (D473): the one supplied, the UUID minted
12532
+ * for an absent one, or the ROWID SQLite assigned on an `INTEGER`
12533
+ * primary key — which is the only place an auto key is knowable.
12534
+ */
12535
+ id: union([string(), number()]) }), { kind: "mutation" }), method(object({
12510
12536
  namespace: string().optional(),
12511
12537
  collection: string(),
12512
12538
  records: array(BulkRecordSchema).readonly()
@@ -12615,8 +12641,17 @@ method(_void(), EngineInfoSchema, { auth: "admin" }), method(object({
12615
12641
  }), array(SettingsRecordSchema).readonly(), { auth: "admin" }), method(object({
12616
12642
  namespace: string().optional(),
12617
12643
  collection: string(),
12618
- record: SettingsRecordSchema
12619
- }), _void(), {
12644
+ record: object({
12645
+ id: string().optional(),
12646
+ data: record(string(), unknown())
12647
+ })
12648
+ }), object({
12649
+ /**
12650
+ * The id the row ACTUALLY got (D473): the one supplied, the UUID minted
12651
+ * for an absent one, or the ROWID SQLite assigned on an `INTEGER`
12652
+ * primary key — which is the only place an auto key is knowable.
12653
+ */
12654
+ id: union([string(), number()]) }), {
12620
12655
  kind: "mutation",
12621
12656
  auth: "admin"
12622
12657
  }), method(object({
@@ -19524,7 +19559,20 @@ var TrackSchema = object({
19524
19559
  ...TrackRetrainFields
19525
19560
  });
19526
19561
  var BaseEventFields = {
19527
- id: string(),
19562
+ /**
19563
+ * A SQLite ROWID, assigned by the database (D474).
19564
+ *
19565
+ * Was a 36-character UUID and cost 263 MB of a 1 117 MB database — paid
19566
+ * TWICE per row, in the row and in the primary-key index, across 2.1 million
19567
+ * motion, audio and object events. An `INTEGER PRIMARY KEY` in SQLite **is**
19568
+ * the rowid: the table itself is that B-tree, so the index stops existing
19569
+ * rather than getting smaller. No shorter string does that.
19570
+ *
19571
+ * Defined once here for all three event kinds, which is why they move
19572
+ * together: a per-table migration would have forked this and
19573
+ * `COMMON_BASE_COLUMNS` and reunited them two stages later.
19574
+ */
19575
+ id: number().int(),
19528
19576
  deviceId: number(),
19529
19577
  timestamp: number()
19530
19578
  };
@@ -19543,7 +19591,34 @@ var MotionEventSchema = object({
19543
19591
  /** Omitted in slim projection. */
19544
19592
  frameHeight: number().optional(),
19545
19593
  /** Populated by B5 (recording playback URL for this event). */
19546
- mediaUrl: string().optional()
19594
+ mediaUrl: string().optional(),
19595
+ /**
19596
+ * One row per motion EPISODE, not one per push (D475). `null` while the
19597
+ * episode is still open — a further rising edge extends it in place rather
19598
+ * than inserting a new row. Set once, at close, to `lastOnAt - startedAt`
19599
+ * (the span from the first rising edge to the LAST one, deliberately NOT
19600
+ * `closedAt - startedAt` — the close delay is a quiet CONFIRMATION, not
19601
+ * movement, and folding it in would report `MOTION_CLOSE_AFTER_MS` of
19602
+ * motion for an instantaneous trigger).
19603
+ *
19604
+ * **Absent** (not merely `null`) on a row written before D475 — that means
19605
+ * "closed the old way, before this column existed", never "still open".
19606
+ * Nothing in this codebase may read an absent `durationMs` as an open
19607
+ * episode; only `null` means open.
19608
+ */
19609
+ durationMs: number().nullable().optional(),
19610
+ /**
19611
+ * Ms offsets from `timestamp` (the episode's own first rising edge, so the
19612
+ * first entry is always `0`) of every genuine off→on transition the
19613
+ * episode saw — "ogni evento on si deve salvare" (D475). NOT one entry per
19614
+ * push: a firmware source that keepalives at ~1 Hz for the whole burst
19615
+ * (Reolink, Hikvision) produces exactly one edge; a source that reports an
19616
+ * explicit `false` mid-episode and then resumes before the quiet window
19617
+ * elapses produces another. Stored compactly — see `motion-edge-codec.ts`
19618
+ * — and decoded back to this shape on read. Absent/empty on a legacy row,
19619
+ * which must never be read as "no episode happened here".
19620
+ */
19621
+ edges: array(number()).readonly().optional()
19547
19622
  });
19548
19623
  /**
19549
19624
  * Which detection SOURCE produced an object event. `pipeline` = the ML
@@ -19598,6 +19673,23 @@ var ObjectEventSchema = object({
19598
19673
  * includes it (it is light). Absent on rows written before this field.
19599
19674
  */
19600
19675
  frameId: string().optional(),
19676
+ /**
19677
+ * A PRODUCER-chosen key that makes a synthetic event's emission idempotent
19678
+ * (D474).
19679
+ *
19680
+ * Only the package detector writes it, and it exists because the event id
19681
+ * stopped being choosable: the delivery and pick-up rows used to BE their
19682
+ * dedupe key (`pa-pkg-<entryId>-delivered`), which is how "never emit a
19683
+ * second delivery for this entry" survived a restart. An `INTEGER` rowid is
19684
+ * assigned by SQLite, so that key had to move off the primary key rather
19685
+ * than be dropped — a detector that cannot recognise its own row re-delivers
19686
+ * every parcel on every boot.
19687
+ *
19688
+ * Absent on every other object event, and on every row written before this
19689
+ * field. Never a substitute for `id`: it is unique per (producer, occasion),
19690
+ * not per row, and nothing addresses a row by it.
19691
+ */
19692
+ idempotencyKey: string().optional(),
19601
19693
  /** Omitted in slim projection. */
19602
19694
  trackId: string().optional(),
19603
19695
  className: string(),
@@ -27847,26 +27939,59 @@ authKey: string().optional() }), object({
27847
27939
  /** Human-readable error when `ok: false`. */
27848
27940
  error: string().optional()
27849
27941
  }), { kind: "mutation" });
27850
- /**
27851
- * Hardware / firmware motion sensor cap — binary detected state plus
27852
- * a timestamp of the last observation. Distinct from
27853
- * `motion-detection.cap.ts` which owns the LOCAL ML motion pipeline;
27854
- * `motion` is the lightweight readout from on-camera motion (Reolink
27855
- * `GetMdState`, Baichuan push `type: motion`, ONVIF analytics).
27856
- *
27857
- * Native-motion providers also fan out to `detection.camera-native`
27858
- * with `source: 'onboard'` so cross-cutting system services
27859
- * (alert-center, advanced-notifier) can subscribe once and receive
27860
- * motion from every camera.
27861
- */
27862
27942
  var MotionStatusSchema = object({
27863
27943
  detected: boolean(),
27864
27944
  /** Ms epoch of the last detected-true observation. Null if never detected. */
27865
27945
  lastDetectedAt: number().nullable(),
27866
27946
  /**
27867
- * Ms after which `detected` auto-reverts to false if no fresh push
27868
- * arrives. Null means the provider leaves detected state until a
27869
- * native "clear" event.
27947
+ * `MOTION_CLOSE_AFTER_MS` while `detected: true` on a `Camera` device,
27948
+ * `null` while false and on every `Sensor` device (D475) — see that
27949
+ * constant's doc for the one-authority rule.
27950
+ *
27951
+ * ## Reading this field still arms nothing
27952
+ *
27953
+ * It reads like an instruction to the consumer ("revert after N ms if
27954
+ * no fresh push arrives") and it is not one: nothing in this repo reads
27955
+ * the LIVE cap value to drive a timer. `pipeline-analytics`'s motion-episode
27956
+ * close DOES now use the same number — `MOTION_CLOSE_AFTER_MS` — but as an
27957
+ * imported constant, not as a read of `device.state.motion.value`, so this
27958
+ * field stays what it always was: DESCRIPTIVE output, mirroring an answer
27959
+ * computed elsewhere. Building a self-clear timer out of a READ of this
27960
+ * field would add a second falling-edge authority beside whichever one
27961
+ * already owns the device, and two that can disagree are worse than one.
27962
+ * Consumers that need a falling edge SHAPED differently — held open across
27963
+ * a flapping source — debounce on their own side and say so, as
27964
+ * `addon-export-alexa/src/motion-clear-hold.ts` and
27965
+ * `addon-export-hap`'s `RESET_DEBOUNCE_MS` both do.
27966
+ *
27967
+ * ## Who writes it
27968
+ *
27969
+ * - **Cameras** — the runner's phase machine, `active → watching` on
27970
+ * `cooldown_expired`, which then writes this slice with
27971
+ * `detected: false` (`handlePhaseChanged` in
27972
+ * `pipeline-runner/index.ts`). It produces the FALLING edge, which
27973
+ * matters most for the sources that only ever push a rising one:
27974
+ * Reolink emits `MotionOnMotionChanged { detected: true }` and never
27975
+ * a false.
27976
+ * - **Sensors** (Home Assistant binary sensors, Homematic) — the
27977
+ * provider pushes the false itself, from the upstream system's own
27978
+ * state change. No phase machine is involved.
27979
+ *
27980
+ * ### The phase machine is CANONICAL, not sole — and that is a defect
27981
+ *
27982
+ * An earlier revision of this docblock (mine, 2026-09-12) claimed the
27983
+ * phase machine is the sole writer for a camera. It is not.
27984
+ * `hikvision-camera.ts:3464` and `amcrest-camera.ts:445` both call
27985
+ * `setCapSlice(motionCapability, …)` on their own rising edge, and
27986
+ * Hikvision's comment says why: it read THIS docblock, agreed the
27987
+ * runner is canonical, and wrote anyway to avoid per-tick churn. So
27988
+ * two authorities can disagree about one slice, which this repo
27989
+ * forbids, and the doc said otherwise — which is worse than saying
27990
+ * nothing, because it reads as verification.
27991
+ *
27992
+ * This predates D475 and is not fixed there: the fix touches every
27993
+ * camera provider. Recorded in D475's Consequences. Do not restore the
27994
+ * "sole writer" wording without also removing the other writers.
27870
27995
  */
27871
27996
  autoClearAfterMs: number().nullable()
27872
27997
  });
@@ -27936,7 +28061,11 @@ onMotionChanged: { data: MotionOnMotionChangedDataSchema } },
27936
28061
  */
27937
28062
  runtimeState: MotionStatusSchema,
27938
28063
  /**
27939
- * Runtime-state durability: **session** — self-clearing by construction (`autoClearAfterMs`); a restored `detected: true` is a frozen event, and the next frame re-publishes the real one.
28064
+ * Runtime-state durability: **session** — every writer of this slice
28065
+ * writes only on an EDGE, so a restored `detected: true` would stay
28066
+ * frozen until the next one instead of being corrected. The next edge
28067
+ * re-publishes the real state. (On who the writers are, and why there
28068
+ * is more than one, see `autoClearAfterMs` above.)
27940
28069
  *
27941
28070
  * See `RuntimeStateDurability`. Enforced by
27942
28071
  * `scripts/check-runtime-state-durability.ts`.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@camstack/addon-provider-gree",
3
- "version": "0.2.96",
3
+ "version": "0.2.97",
4
4
  "description": "Gree air-conditioner device-provider addon for CamStack — wraps the @apocaliss92/nodegree local-UDP client (LAN discovery + AES control), exposing climate-control and fan-control",
5
5
  "keywords": [
6
6
  "camstack",