esp-rainmaker-cli 1.11.1__tar.gz → 1.13.0__tar.gz

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 (127) hide show
  1. {esp_rainmaker_cli-1.11.1 → esp_rainmaker_cli-1.13.0}/PKG-INFO +30 -2
  2. {esp_rainmaker_cli-1.11.1 → esp_rainmaker_cli-1.13.0}/esp_rainmaker_cli.egg-info/PKG-INFO +30 -2
  3. {esp_rainmaker_cli-1.11.1 → esp_rainmaker_cli-1.13.0}/esp_rainmaker_cli.egg-info/SOURCES.txt +7 -0
  4. {esp_rainmaker_cli-1.11.1 → esp_rainmaker_cli-1.13.0}/esp_rainmaker_cli.egg-info/requires.txt +6 -1
  5. {esp_rainmaker_cli-1.11.1 → esp_rainmaker_cli-1.13.0}/rainmaker/rainmaker.py +156 -8
  6. {esp_rainmaker_cli-1.11.1 → esp_rainmaker_cli-1.13.0}/rainmaker/version.py +1 -1
  7. esp_rainmaker_cli-1.13.0/rmaker_cmd/automation.py +217 -0
  8. esp_rainmaker_cli-1.13.0/rmaker_cmd/cache.py +160 -0
  9. {esp_rainmaker_cli-1.11.1 → esp_rainmaker_cli-1.13.0}/rmaker_cmd/node.py +355 -67
  10. esp_rainmaker_cli-1.13.0/rmaker_cmd/stream.py +203 -0
  11. {esp_rainmaker_cli-1.11.1 → esp_rainmaker_cli-1.13.0}/rmaker_cmd/user.py +5 -1
  12. esp_rainmaker_cli-1.13.0/rmaker_lib/aws_credentials.py +220 -0
  13. esp_rainmaker_cli-1.13.0/rmaker_lib/kvs_streaming.py +2014 -0
  14. esp_rainmaker_cli-1.13.0/rmaker_lib/node_cache.py +327 -0
  15. {esp_rainmaker_cli-1.11.1 → esp_rainmaker_cli-1.13.0}/rmaker_lib/profile_manager.py +40 -3
  16. {esp_rainmaker_cli-1.11.1 → esp_rainmaker_cli-1.13.0}/rmaker_lib/session.py +79 -0
  17. esp_rainmaker_cli-1.13.0/rmaker_lib/session_store.py +138 -0
  18. {esp_rainmaker_cli-1.11.1 → esp_rainmaker_cli-1.13.0}/rmaker_tools/common/security/security1.py +76 -8
  19. {esp_rainmaker_cli-1.11.1 → esp_rainmaker_cli-1.13.0}/rmaker_tools/common/transport/transport_http.py +38 -4
  20. {esp_rainmaker_cli-1.11.1 → esp_rainmaker_cli-1.13.0}/rmaker_tools/rmaker_local_ctrl/esp_rainmaker_ctrl.py +50 -10
  21. esp_rainmaker_cli-1.13.0/rmaker_tools/rmaker_local_ctrl/integration.py +430 -0
  22. {esp_rainmaker_cli-1.11.1 → esp_rainmaker_cli-1.13.0}/setup.py +2 -0
  23. esp_rainmaker_cli-1.11.1/rmaker_tools/rmaker_local_ctrl/integration.py +0 -86
  24. {esp_rainmaker_cli-1.11.1 → esp_rainmaker_cli-1.13.0}/LICENSE +0 -0
  25. {esp_rainmaker_cli-1.11.1 → esp_rainmaker_cli-1.13.0}/README.md +0 -0
  26. {esp_rainmaker_cli-1.11.1 → esp_rainmaker_cli-1.13.0}/esp_rainmaker_cli.egg-info/dependency_links.txt +0 -0
  27. {esp_rainmaker_cli-1.11.1 → esp_rainmaker_cli-1.13.0}/esp_rainmaker_cli.egg-info/entry_points.txt +0 -0
  28. {esp_rainmaker_cli-1.11.1 → esp_rainmaker_cli-1.13.0}/esp_rainmaker_cli.egg-info/top_level.txt +0 -0
  29. {esp_rainmaker_cli-1.11.1 → esp_rainmaker_cli-1.13.0}/rainmaker/__init__.py +0 -0
  30. {esp_rainmaker_cli-1.11.1 → esp_rainmaker_cli-1.13.0}/rmaker_cmd/__init__.py +0 -0
  31. {esp_rainmaker_cli-1.11.1 → esp_rainmaker_cli-1.13.0}/rmaker_cmd/browserlogin.py +0 -0
  32. {esp_rainmaker_cli-1.11.1 → esp_rainmaker_cli-1.13.0}/rmaker_cmd/cmd_response.py +0 -0
  33. {esp_rainmaker_cli-1.11.1 → esp_rainmaker_cli-1.13.0}/rmaker_cmd/group.py +0 -0
  34. {esp_rainmaker_cli-1.11.1 → esp_rainmaker_cli-1.13.0}/rmaker_cmd/html/welcome.html +0 -0
  35. {esp_rainmaker_cli-1.11.1 → esp_rainmaker_cli-1.13.0}/rmaker_cmd/provision.py +0 -0
  36. {esp_rainmaker_cli-1.11.1 → esp_rainmaker_cli-1.13.0}/rmaker_cmd/test.py +0 -0
  37. {esp_rainmaker_cli-1.11.1 → esp_rainmaker_cli-1.13.0}/rmaker_lib/__init__.py +0 -0
  38. {esp_rainmaker_cli-1.11.1 → esp_rainmaker_cli-1.13.0}/rmaker_lib/cmd_response.py +0 -0
  39. {esp_rainmaker_cli-1.11.1 → esp_rainmaker_cli-1.13.0}/rmaker_lib/configmanager.py +0 -0
  40. {esp_rainmaker_cli-1.11.1 → esp_rainmaker_cli-1.13.0}/rmaker_lib/constants.py +0 -0
  41. {esp_rainmaker_cli-1.11.1 → esp_rainmaker_cli-1.13.0}/rmaker_lib/device.py +0 -0
  42. {esp_rainmaker_cli-1.11.1 → esp_rainmaker_cli-1.13.0}/rmaker_lib/envval.py +0 -0
  43. {esp_rainmaker_cli-1.11.1 → esp_rainmaker_cli-1.13.0}/rmaker_lib/exceptions.py +0 -0
  44. {esp_rainmaker_cli-1.11.1 → esp_rainmaker_cli-1.13.0}/rmaker_lib/local_control.py +0 -0
  45. {esp_rainmaker_cli-1.11.1 → esp_rainmaker_cli-1.13.0}/rmaker_lib/logger.py +0 -0
  46. {esp_rainmaker_cli-1.11.1 → esp_rainmaker_cli-1.13.0}/rmaker_lib/node.py +0 -0
  47. {esp_rainmaker_cli-1.11.1 → esp_rainmaker_cli-1.13.0}/rmaker_lib/profile_utils.py +0 -0
  48. {esp_rainmaker_cli-1.11.1 → esp_rainmaker_cli-1.13.0}/rmaker_lib/schedule_utils.py +0 -0
  49. {esp_rainmaker_cli-1.11.1 → esp_rainmaker_cli-1.13.0}/rmaker_lib/serverconfig.py +0 -0
  50. {esp_rainmaker_cli-1.11.1 → esp_rainmaker_cli-1.13.0}/rmaker_lib/service.py +0 -0
  51. {esp_rainmaker_cli-1.11.1 → esp_rainmaker_cli-1.13.0}/rmaker_lib/simple_local_control.py +0 -0
  52. {esp_rainmaker_cli-1.11.1 → esp_rainmaker_cli-1.13.0}/rmaker_lib/user.py +0 -0
  53. {esp_rainmaker_cli-1.11.1 → esp_rainmaker_cli-1.13.0}/rmaker_tools/__init__.py +0 -0
  54. {esp_rainmaker_cli-1.11.1 → esp_rainmaker_cli-1.13.0}/rmaker_tools/common/__init__.py +0 -0
  55. {esp_rainmaker_cli-1.11.1 → esp_rainmaker_cli-1.13.0}/rmaker_tools/common/discovery/__init__.py +0 -0
  56. {esp_rainmaker_cli-1.11.1 → esp_rainmaker_cli-1.13.0}/rmaker_tools/common/discovery/mdns_discovery.py +0 -0
  57. {esp_rainmaker_cli-1.11.1 → esp_rainmaker_cli-1.13.0}/rmaker_tools/common/prov/__init__.py +0 -0
  58. {esp_rainmaker_cli-1.11.1 → esp_rainmaker_cli-1.13.0}/rmaker_tools/common/prov/wifi_ctrl.py +0 -0
  59. {esp_rainmaker_cli-1.11.1 → esp_rainmaker_cli-1.13.0}/rmaker_tools/common/prov/wifi_prov.py +0 -0
  60. {esp_rainmaker_cli-1.11.1 → esp_rainmaker_cli-1.13.0}/rmaker_tools/common/prov/wifi_scan.py +0 -0
  61. {esp_rainmaker_cli-1.11.1 → esp_rainmaker_cli-1.13.0}/rmaker_tools/common/security/__init__.py +0 -0
  62. {esp_rainmaker_cli-1.11.1 → esp_rainmaker_cli-1.13.0}/rmaker_tools/common/security/security.py +0 -0
  63. {esp_rainmaker_cli-1.11.1 → esp_rainmaker_cli-1.13.0}/rmaker_tools/common/security/security0.py +0 -0
  64. {esp_rainmaker_cli-1.11.1 → esp_rainmaker_cli-1.13.0}/rmaker_tools/common/security/security2.py +0 -0
  65. {esp_rainmaker_cli-1.11.1 → esp_rainmaker_cli-1.13.0}/rmaker_tools/common/security/srp6a.py +0 -0
  66. {esp_rainmaker_cli-1.11.1 → esp_rainmaker_cli-1.13.0}/rmaker_tools/common/transport/__init__.py +0 -0
  67. {esp_rainmaker_cli-1.11.1 → esp_rainmaker_cli-1.13.0}/rmaker_tools/common/transport/ble_cli.py +0 -0
  68. {esp_rainmaker_cli-1.11.1 → esp_rainmaker_cli-1.13.0}/rmaker_tools/common/transport/transport.py +0 -0
  69. {esp_rainmaker_cli-1.11.1 → esp_rainmaker_cli-1.13.0}/rmaker_tools/common/transport/transport_ble.py +0 -0
  70. {esp_rainmaker_cli-1.11.1 → esp_rainmaker_cli-1.13.0}/rmaker_tools/common/transport/transport_console.py +0 -0
  71. {esp_rainmaker_cli-1.11.1 → esp_rainmaker_cli-1.13.0}/rmaker_tools/common/utils/__init__.py +0 -0
  72. {esp_rainmaker_cli-1.11.1 → esp_rainmaker_cli-1.13.0}/rmaker_tools/common/utils/convenience.py +0 -0
  73. {esp_rainmaker_cli-1.11.1 → esp_rainmaker_cli-1.13.0}/rmaker_tools/rmaker_claim/__init__.py +0 -0
  74. {esp_rainmaker_cli-1.11.1 → esp_rainmaker_cli-1.13.0}/rmaker_tools/rmaker_claim/claim.py +0 -0
  75. {esp_rainmaker_cli-1.11.1 → esp_rainmaker_cli-1.13.0}/rmaker_tools/rmaker_claim/claim_config.py +0 -0
  76. {esp_rainmaker_cli-1.11.1 → esp_rainmaker_cli-1.13.0}/rmaker_tools/rmaker_local_ctrl/__init__.py +0 -0
  77. {esp_rainmaker_cli-1.11.1 → esp_rainmaker_cli-1.13.0}/rmaker_tools/rmaker_local_ctrl/constants_pb2.py +0 -0
  78. {esp_rainmaker_cli-1.11.1 → esp_rainmaker_cli-1.13.0}/rmaker_tools/rmaker_local_ctrl/esp_local_ctrl.py +0 -0
  79. {esp_rainmaker_cli-1.11.1 → esp_rainmaker_cli-1.13.0}/rmaker_tools/rmaker_local_ctrl/esp_local_ctrl_pb2.py +0 -0
  80. {esp_rainmaker_cli-1.11.1 → esp_rainmaker_cli-1.13.0}/rmaker_tools/rmaker_local_ctrl/esp_prov.py +0 -0
  81. {esp_rainmaker_cli-1.11.1 → esp_rainmaker_cli-1.13.0}/rmaker_tools/rmaker_local_ctrl/esp_rmaker_prov_local_ctrl_pb2.py +0 -0
  82. {esp_rainmaker_cli-1.11.1 → esp_rainmaker_cli-1.13.0}/rmaker_tools/rmaker_local_ctrl/proto/__init__.py +0 -0
  83. {esp_rainmaker_cli-1.11.1 → esp_rainmaker_cli-1.13.0}/rmaker_tools/rmaker_local_ctrl/proto/proto_lc.py +0 -0
  84. {esp_rainmaker_cli-1.11.1 → esp_rainmaker_cli-1.13.0}/rmaker_tools/rmaker_local_ctrl/prov/__init__.py +0 -0
  85. {esp_rainmaker_cli-1.11.1 → esp_rainmaker_cli-1.13.0}/rmaker_tools/rmaker_local_ctrl/prov/custom_prov.py +0 -0
  86. {esp_rainmaker_cli-1.11.1 → esp_rainmaker_cli-1.13.0}/rmaker_tools/rmaker_local_ctrl/prov/wifi_ctrl.py +0 -0
  87. {esp_rainmaker_cli-1.11.1 → esp_rainmaker_cli-1.13.0}/rmaker_tools/rmaker_local_ctrl/raw_config.py +0 -0
  88. {esp_rainmaker_cli-1.11.1 → esp_rainmaker_cli-1.13.0}/rmaker_tools/rmaker_local_ctrl/raw_params.py +0 -0
  89. {esp_rainmaker_cli-1.11.1 → esp_rainmaker_cli-1.13.0}/rmaker_tools/rmaker_local_ctrl/sec0_pb2.py +0 -0
  90. {esp_rainmaker_cli-1.11.1 → esp_rainmaker_cli-1.13.0}/rmaker_tools/rmaker_local_ctrl/sec1_pb2.py +0 -0
  91. {esp_rainmaker_cli-1.11.1 → esp_rainmaker_cli-1.13.0}/rmaker_tools/rmaker_local_ctrl/sec2_pb2.py +0 -0
  92. {esp_rainmaker_cli-1.11.1 → esp_rainmaker_cli-1.13.0}/rmaker_tools/rmaker_local_ctrl/security/__init__.py +0 -0
  93. {esp_rainmaker_cli-1.11.1 → esp_rainmaker_cli-1.13.0}/rmaker_tools/rmaker_local_ctrl/session_pb2.py +0 -0
  94. {esp_rainmaker_cli-1.11.1 → esp_rainmaker_cli-1.13.0}/rmaker_tools/rmaker_local_ctrl/transport/__init__.py +0 -0
  95. {esp_rainmaker_cli-1.11.1 → esp_rainmaker_cli-1.13.0}/rmaker_tools/rmaker_local_ctrl/utils/__init__.py +0 -0
  96. {esp_rainmaker_cli-1.11.1 → esp_rainmaker_cli-1.13.0}/rmaker_tools/rmaker_prov/__init__.py +0 -0
  97. {esp_rainmaker_cli-1.11.1 → esp_rainmaker_cli-1.13.0}/rmaker_tools/rmaker_prov/challenge_response.py +0 -0
  98. {esp_rainmaker_cli-1.11.1 → esp_rainmaker_cli-1.13.0}/rmaker_tools/rmaker_prov/config/__init__.py +0 -0
  99. {esp_rainmaker_cli-1.11.1 → esp_rainmaker_cli-1.13.0}/rmaker_tools/rmaker_prov/config/custom_cloud_config_pb2.py +0 -0
  100. {esp_rainmaker_cli-1.11.1 → esp_rainmaker_cli-1.13.0}/rmaker_tools/rmaker_prov/config/esp_rmaker_chal_resp_pb2.py +0 -0
  101. {esp_rainmaker_cli-1.11.1 → esp_rainmaker_cli-1.13.0}/rmaker_tools/rmaker_prov/config/esp_rmaker_claim_pb2.py +0 -0
  102. {esp_rainmaker_cli-1.11.1 → esp_rainmaker_cli-1.13.0}/rmaker_tools/rmaker_prov/config/esp_rmaker_user_mapping_pb2.py +0 -0
  103. {esp_rainmaker_cli-1.11.1 → esp_rainmaker_cli-1.13.0}/rmaker_tools/rmaker_prov/esp_rainmaker_prov.py +0 -0
  104. {esp_rainmaker_cli-1.11.1 → esp_rainmaker_cli-1.13.0}/rmaker_tools/rmaker_prov/on_network_chal_resp.py +0 -0
  105. {esp_rainmaker_cli-1.11.1 → esp_rainmaker_cli-1.13.0}/rmaker_tools/rmaker_prov/proto/__init__.py +0 -0
  106. {esp_rainmaker_cli-1.11.1 → esp_rainmaker_cli-1.13.0}/rmaker_tools/rmaker_prov/protocomm/__init__.py +0 -0
  107. {esp_rainmaker_cli-1.11.1 → esp_rainmaker_cli-1.13.0}/rmaker_tools/rmaker_prov/protocomm/python/__init__.py +0 -0
  108. {esp_rainmaker_cli-1.11.1 → esp_rainmaker_cli-1.13.0}/rmaker_tools/rmaker_prov/protocomm/python/constants_pb2.py +0 -0
  109. {esp_rainmaker_cli-1.11.1 → esp_rainmaker_cli-1.13.0}/rmaker_tools/rmaker_prov/protocomm/python/sec0_pb2.py +0 -0
  110. {esp_rainmaker_cli-1.11.1 → esp_rainmaker_cli-1.13.0}/rmaker_tools/rmaker_prov/protocomm/python/sec1_pb2.py +0 -0
  111. {esp_rainmaker_cli-1.11.1 → esp_rainmaker_cli-1.13.0}/rmaker_tools/rmaker_prov/protocomm/python/sec2_pb2.py +0 -0
  112. {esp_rainmaker_cli-1.11.1 → esp_rainmaker_cli-1.13.0}/rmaker_tools/rmaker_prov/protocomm/python/session_pb2.py +0 -0
  113. {esp_rainmaker_cli-1.11.1 → esp_rainmaker_cli-1.13.0}/rmaker_tools/rmaker_prov/prov/__init__.py +0 -0
  114. {esp_rainmaker_cli-1.11.1 → esp_rainmaker_cli-1.13.0}/rmaker_tools/rmaker_prov/prov/prov_util.py +0 -0
  115. {esp_rainmaker_cli-1.11.1 → esp_rainmaker_cli-1.13.0}/rmaker_tools/rmaker_prov/prov/user_mapping.py +0 -0
  116. {esp_rainmaker_cli-1.11.1 → esp_rainmaker_cli-1.13.0}/rmaker_tools/rmaker_prov/security/__init__.py +0 -0
  117. {esp_rainmaker_cli-1.11.1 → esp_rainmaker_cli-1.13.0}/rmaker_tools/rmaker_prov/transport/__init__.py +0 -0
  118. {esp_rainmaker_cli-1.11.1 → esp_rainmaker_cli-1.13.0}/rmaker_tools/rmaker_prov/utils/__init__.py +0 -0
  119. {esp_rainmaker_cli-1.11.1 → esp_rainmaker_cli-1.13.0}/rmaker_tools/rmaker_prov/wifi_provisioning/__init__.py +0 -0
  120. {esp_rainmaker_cli-1.11.1 → esp_rainmaker_cli-1.13.0}/rmaker_tools/rmaker_prov/wifi_provisioning/python/__init__.py +0 -0
  121. {esp_rainmaker_cli-1.11.1 → esp_rainmaker_cli-1.13.0}/rmaker_tools/rmaker_prov/wifi_provisioning/python/wifi_config_pb2.py +0 -0
  122. {esp_rainmaker_cli-1.11.1 → esp_rainmaker_cli-1.13.0}/rmaker_tools/rmaker_prov/wifi_provisioning/python/wifi_constants_pb2.py +0 -0
  123. {esp_rainmaker_cli-1.11.1 → esp_rainmaker_cli-1.13.0}/rmaker_tools/rmaker_prov/wifi_provisioning/python/wifi_ctrl_pb2.py +0 -0
  124. {esp_rainmaker_cli-1.11.1 → esp_rainmaker_cli-1.13.0}/rmaker_tools/rmaker_prov/wifi_provisioning/python/wifi_scan_pb2.py +0 -0
  125. {esp_rainmaker_cli-1.11.1 → esp_rainmaker_cli-1.13.0}/server_cert/__init__.py +0 -0
  126. {esp_rainmaker_cli-1.11.1 → esp_rainmaker_cli-1.13.0}/server_cert/server_cert.pem +0 -0
  127. {esp_rainmaker_cli-1.11.1 → esp_rainmaker_cli-1.13.0}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: esp-rainmaker-cli
3
- Version: 1.11.1
3
+ Version: 1.13.0
4
4
  Summary: A python utility to perform host based claiming
5
5
  Home-page: https://github.com/espressif/esp-rainmaker-cli
6
6
  Author: Espressif Systems
@@ -18,6 +18,8 @@ Classifier: Topic :: Software Development :: Embedded Systems
18
18
  Classifier: Programming Language :: Python :: 3.9
19
19
  Classifier: Programming Language :: Python :: 3.10
20
20
  Classifier: Programming Language :: Python :: 3.11
21
+ Classifier: Programming Language :: Python :: 3.12
22
+ Classifier: Programming Language :: Python :: 3.13
21
23
  Requires-Python: >=3.9
22
24
  Description-Content-Type: text/markdown
23
25
  License-File: LICENSE
@@ -32,11 +34,16 @@ Requires-Dist: future>=0.15.2
32
34
  Requires-Dist: pyparsing>=2.4.0
33
35
  Requires-Dist: pyelftools>=0.22
34
36
  Requires-Dist: esptool>=4.4
35
- Requires-Dist: esp-secure-cert-tool==1.0.1
37
+ Requires-Dist: esp-secure-cert-tool>=2.3.6
36
38
  Requires-Dist: setuptools
37
39
  Requires-Dist: esp_idf_nvs_partition_gen>=0.1.6
38
40
  Requires-Dist: bleak>=0.20.0
39
41
  Requires-Dist: zeroconf>=0.131.0
42
+ Requires-Dist: aiortc>=1.6.0
43
+ Requires-Dist: boto3>=1.28.0
44
+ Requires-Dist: websockets>=11.0
45
+ Requires-Dist: opencv-python>=4.8.0
46
+ Requires-Dist: numpy>=1.24.0
40
47
  Dynamic: author
41
48
  Dynamic: classifier
42
49
  Dynamic: description
@@ -115,6 +122,27 @@ Changelog
115
122
 
116
123
  All major changes to ESP RainMaker CLI will be documented in this file.
117
124
 
125
+ ## [1.13.0] - 08-Apr-2026
126
+ ### Added
127
+ - New `stream` command for WebRTC video streaming from ESP RainMaker camera devices via Amazon Kinesis Video Streams (KVS) signaling:
128
+ - Live video display with optional recording to file (`--output` / `-o`)
129
+ - Duration-limited streaming (`--duration`)
130
+ - Parallel ICE server fetch and WebSocket connection for faster stream setup
131
+ - Graceful handling of device disconnects, reboots, and frame timeouts
132
+ - Suppression of macOS FFmpeg duplicate library warnings during import
133
+
134
+ ## [1.12.0] - 19-Feb-2026
135
+ ### Added
136
+ - Local node cache layer for faster `--local` control operations:
137
+ - Caches node details, config, POP, and local control capability per profile/user
138
+ - Disk-based session reuse to skip X25519 handshake on repeated commands
139
+ - Auto-POP resolution from cache, eliminating need for prior cloud `getparams` call
140
+ - Empirical capability discovery (probes sec0 first, then sec1, caches result)
141
+ - Cache disabled by default; enable via `cache enable` or `profile add --cache`
142
+ - New `cache` CLI command group: `enable`, `disable`, `show`, `clear`
143
+ - `--no-cache` flag on `getparams`, `setparams`, `getnodeconfig` for one-shot cache bypass
144
+ - `RM_NODE_CACHE` and `RM_NODE_CACHE_DIR` environment variables for automation control
145
+
118
146
  ## [1.11.1] - 10-Feb-2026
119
147
  - Add support for ecdsa in claiming and make it as default, with an option to fall back to the earlier rsa scheme.
120
148
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: esp-rainmaker-cli
3
- Version: 1.11.1
3
+ Version: 1.13.0
4
4
  Summary: A python utility to perform host based claiming
5
5
  Home-page: https://github.com/espressif/esp-rainmaker-cli
6
6
  Author: Espressif Systems
@@ -18,6 +18,8 @@ Classifier: Topic :: Software Development :: Embedded Systems
18
18
  Classifier: Programming Language :: Python :: 3.9
19
19
  Classifier: Programming Language :: Python :: 3.10
20
20
  Classifier: Programming Language :: Python :: 3.11
21
+ Classifier: Programming Language :: Python :: 3.12
22
+ Classifier: Programming Language :: Python :: 3.13
21
23
  Requires-Python: >=3.9
22
24
  Description-Content-Type: text/markdown
23
25
  License-File: LICENSE
@@ -32,11 +34,16 @@ Requires-Dist: future>=0.15.2
32
34
  Requires-Dist: pyparsing>=2.4.0
33
35
  Requires-Dist: pyelftools>=0.22
34
36
  Requires-Dist: esptool>=4.4
35
- Requires-Dist: esp-secure-cert-tool==1.0.1
37
+ Requires-Dist: esp-secure-cert-tool>=2.3.6
36
38
  Requires-Dist: setuptools
37
39
  Requires-Dist: esp_idf_nvs_partition_gen>=0.1.6
38
40
  Requires-Dist: bleak>=0.20.0
39
41
  Requires-Dist: zeroconf>=0.131.0
42
+ Requires-Dist: aiortc>=1.6.0
43
+ Requires-Dist: boto3>=1.28.0
44
+ Requires-Dist: websockets>=11.0
45
+ Requires-Dist: opencv-python>=4.8.0
46
+ Requires-Dist: numpy>=1.24.0
40
47
  Dynamic: author
41
48
  Dynamic: classifier
42
49
  Dynamic: description
@@ -115,6 +122,27 @@ Changelog
115
122
 
116
123
  All major changes to ESP RainMaker CLI will be documented in this file.
117
124
 
125
+ ## [1.13.0] - 08-Apr-2026
126
+ ### Added
127
+ - New `stream` command for WebRTC video streaming from ESP RainMaker camera devices via Amazon Kinesis Video Streams (KVS) signaling:
128
+ - Live video display with optional recording to file (`--output` / `-o`)
129
+ - Duration-limited streaming (`--duration`)
130
+ - Parallel ICE server fetch and WebSocket connection for faster stream setup
131
+ - Graceful handling of device disconnects, reboots, and frame timeouts
132
+ - Suppression of macOS FFmpeg duplicate library warnings during import
133
+
134
+ ## [1.12.0] - 19-Feb-2026
135
+ ### Added
136
+ - Local node cache layer for faster `--local` control operations:
137
+ - Caches node details, config, POP, and local control capability per profile/user
138
+ - Disk-based session reuse to skip X25519 handshake on repeated commands
139
+ - Auto-POP resolution from cache, eliminating need for prior cloud `getparams` call
140
+ - Empirical capability discovery (probes sec0 first, then sec1, caches result)
141
+ - Cache disabled by default; enable via `cache enable` or `profile add --cache`
142
+ - New `cache` CLI command group: `enable`, `disable`, `show`, `clear`
143
+ - `--no-cache` flag on `getparams`, `setparams`, `getnodeconfig` for one-shot cache bypass
144
+ - `RM_NODE_CACHE` and `RM_NODE_CACHE_DIR` environment variables for automation control
145
+
118
146
  ## [1.11.1] - 10-Feb-2026
119
147
  - Add support for ecdsa in claiming and make it as default, with an option to fall back to the earlier rsa scheme.
120
148
 
@@ -11,30 +11,37 @@ rainmaker/__init__.py
11
11
  rainmaker/rainmaker.py
12
12
  rainmaker/version.py
13
13
  rmaker_cmd/__init__.py
14
+ rmaker_cmd/automation.py
14
15
  rmaker_cmd/browserlogin.py
16
+ rmaker_cmd/cache.py
15
17
  rmaker_cmd/cmd_response.py
16
18
  rmaker_cmd/group.py
17
19
  rmaker_cmd/node.py
18
20
  rmaker_cmd/provision.py
21
+ rmaker_cmd/stream.py
19
22
  rmaker_cmd/test.py
20
23
  rmaker_cmd/user.py
21
24
  rmaker_cmd/html/welcome.html
22
25
  rmaker_lib/__init__.py
26
+ rmaker_lib/aws_credentials.py
23
27
  rmaker_lib/cmd_response.py
24
28
  rmaker_lib/configmanager.py
25
29
  rmaker_lib/constants.py
26
30
  rmaker_lib/device.py
27
31
  rmaker_lib/envval.py
28
32
  rmaker_lib/exceptions.py
33
+ rmaker_lib/kvs_streaming.py
29
34
  rmaker_lib/local_control.py
30
35
  rmaker_lib/logger.py
31
36
  rmaker_lib/node.py
37
+ rmaker_lib/node_cache.py
32
38
  rmaker_lib/profile_manager.py
33
39
  rmaker_lib/profile_utils.py
34
40
  rmaker_lib/schedule_utils.py
35
41
  rmaker_lib/serverconfig.py
36
42
  rmaker_lib/service.py
37
43
  rmaker_lib/session.py
44
+ rmaker_lib/session_store.py
38
45
  rmaker_lib/simple_local_control.py
39
46
  rmaker_lib/user.py
40
47
  rmaker_tools/__init__.py
@@ -9,8 +9,13 @@ future>=0.15.2
9
9
  pyparsing>=2.4.0
10
10
  pyelftools>=0.22
11
11
  esptool>=4.4
12
- esp-secure-cert-tool==1.0.1
12
+ esp-secure-cert-tool>=2.3.6
13
13
  setuptools
14
14
  esp_idf_nvs_partition_gen>=0.1.6
15
15
  bleak>=0.20.0
16
16
  zeroconf>=0.131.0
17
+ aiortc>=1.6.0
18
+ boto3>=1.28.0
19
+ websockets>=11.0
20
+ opencv-python>=4.8.0
21
+ numpy>=1.24.0
@@ -1,6 +1,6 @@
1
1
  #!/usr/bin/env python3
2
2
  #
3
- # SPDX-FileCopyrightText: 2020-2025 Espressif Systems (Shanghai) CO LTD
3
+ # SPDX-FileCopyrightText: 2020-2026 Espressif Systems (Shanghai) CO LTD
4
4
  #
5
5
  # SPDX-License-Identifier: Apache-2.0
6
6
 
@@ -16,6 +16,9 @@ from rmaker_cmd.provision import provision
16
16
  from rmaker_cmd.test import test
17
17
  from rmaker_lib.logger import log
18
18
  from rmaker_cmd.group import group_add, group_remove, group_edit, group_list, group_show, group_add_nodes, group_remove_nodes, group_list_nodes
19
+ from rmaker_cmd.automation import automation_add, automation_edit, automation_remove, automation_get
20
+ from rmaker_cmd.cache import cache_manage
21
+ from rmaker_cmd.stream import stream_video
19
22
 
20
23
  # Import the version
21
24
  from rainmaker.version import VERSION
@@ -102,6 +105,9 @@ def main():
102
105
  type=str,
103
106
  metavar='<description>',
104
107
  help='Description for the custom profile (optional)')
108
+ profile_add_parser.add_argument('--cache',
109
+ action='store_true',
110
+ help='Enable node cache for this profile')
105
111
  profile_add_parser.set_defaults(func=profile_add)
106
112
 
107
113
  # profile remove
@@ -112,6 +118,33 @@ def main():
112
118
  help='Name of the profile to remove')
113
119
  profile_remove_parser.set_defaults(func=profile_remove)
114
120
 
121
+ # Cache management commands
122
+ cache_parser = subparsers.add_parser("cache",
123
+ help="Manage local node cache")
124
+ cache_subparsers = cache_parser.add_subparsers(dest='cache_command', help='Cache operations')
125
+
126
+ cache_enable_parser = cache_subparsers.add_parser('enable', help='Enable node cache for current profile')
127
+ cache_enable_parser.set_defaults(func=cache_manage)
128
+
129
+ cache_disable_parser = cache_subparsers.add_parser('disable', help='Disable node cache for current profile')
130
+ cache_disable_parser.set_defaults(func=cache_manage)
131
+
132
+ cache_show_parser = cache_subparsers.add_parser('show', help='Show cached data')
133
+ cache_show_parser.add_argument('--node',
134
+ type=str,
135
+ metavar='<nodeid>',
136
+ help='Node ID to show cache for (omit for all)')
137
+ add_profile_argument(cache_show_parser)
138
+ cache_show_parser.set_defaults(func=cache_manage)
139
+
140
+ cache_clear_parser = cache_subparsers.add_parser('clear', help='Clear cached data')
141
+ cache_clear_parser.add_argument('--node',
142
+ type=str,
143
+ metavar='<nodeid>',
144
+ help='Node ID to clear cache for (omit for all)')
145
+ add_profile_argument(cache_clear_parser)
146
+ cache_clear_parser.set_defaults(func=cache_manage)
147
+
115
148
  signup_parser = subparsers.add_parser("signup",
116
149
  help="Sign up for ESP RainMaker")
117
150
  signup_parser.add_argument('user_name',
@@ -245,8 +278,8 @@ def main():
245
278
  getnodeconfig_parser.add_argument('--sec_ver',
246
279
  type=int,
247
280
  choices=[0, 1, 2],
248
- default=1,
249
- help='Security version for local control')
281
+ default=None,
282
+ help='Security version for local control (default: 1)')
250
283
  getnodeconfig_parser.add_argument('--local-raw',
251
284
  action='store_true',
252
285
  help='Use local control via raw endpoints (get_config with fragmentation) instead of esp_local_ctrl')
@@ -259,6 +292,13 @@ def main():
259
292
  getnodeconfig_parser.add_argument('--proxy-report',
260
293
  action='store_true',
261
294
  help='Automatically use current timestamp and POST signed response to proxy API endpoint')
295
+ getnodeconfig_parser.add_argument('--auto',
296
+ action='store_true',
297
+ help='Try local control first, fall back to cloud if local fails')
298
+ getnodeconfig_parser.add_argument('--no-cache',
299
+ action='store_true',
300
+ dest='no_cache',
301
+ help='Skip local cache for this invocation')
262
302
  add_profile_argument(getnodeconfig_parser)
263
303
  getnodeconfig_parser.set_defaults(func=get_node_config)
264
304
 
@@ -308,8 +348,8 @@ def main():
308
348
  setparams_parser.add_argument('--sec_ver',
309
349
  type=int,
310
350
  choices=[0, 1, 2],
311
- default=1,
312
- help='Security version for local control')
351
+ default=None,
352
+ help='Security version for local control (default: 1)')
313
353
  setparams_parser.add_argument('--local-raw',
314
354
  action='store_true',
315
355
  help='Use local control via raw endpoints (get_params/set_params) instead of esp_local_ctrl')
@@ -317,7 +357,13 @@ def main():
317
357
  type=str,
318
358
  help='Device name for BLE transport (e.g., PROV_aaf824). Required when using --transport ble')
319
359
 
320
- # Note: setparams_data_group is mutually exclusive group, so we add profile to the parent
360
+ setparams_parser.add_argument('--auto',
361
+ action='store_true',
362
+ help='Try local control first, fall back to cloud if local fails')
363
+ setparams_parser.add_argument('--no-cache',
364
+ action='store_true',
365
+ dest='no_cache',
366
+ help='Skip local cache for this invocation')
321
367
  add_profile_argument(setparams_parser)
322
368
  setparams_parser.set_defaults(func=set_params)
323
369
 
@@ -345,8 +391,8 @@ def main():
345
391
  getparams_parser.add_argument('--sec_ver',
346
392
  type=int,
347
393
  choices=[0, 1, 2],
348
- default=1,
349
- help='Security version for local control')
394
+ default=None,
395
+ help='Security version for local control (default: 1)')
350
396
  getparams_parser.add_argument('--local-raw',
351
397
  action='store_true',
352
398
  help='Use local control via raw endpoints (get_params/set_params) instead of esp_local_ctrl')
@@ -362,6 +408,13 @@ def main():
362
408
  getparams_parser.add_argument('--init',
363
409
  action='store_true',
364
410
  help='Use initparams API endpoint instead of params (only with --proxy-report)')
411
+ getparams_parser.add_argument('--auto',
412
+ action='store_true',
413
+ help='Try local control first, fall back to cloud if local fails')
414
+ getparams_parser.add_argument('--no-cache',
415
+ action='store_true',
416
+ dest='no_cache',
417
+ help='Skip local cache for this invocation')
365
418
  add_profile_argument(getparams_parser)
366
419
  getparams_parser.set_defaults(func=get_params)
367
420
 
@@ -858,6 +911,76 @@ def main():
858
911
  add_profile_argument(group_list_nodes_parser)
859
912
  group_list_nodes_parser.set_defaults(func=group_list_nodes)
860
913
 
914
+ # Automation Management
915
+ automation_parser = subparsers.add_parser('automations',
916
+ help='Manage automation triggers')
917
+ automation_parser.set_defaults(func=lambda vars=None: automation_parser.print_help())
918
+ automation_subparsers = automation_parser.add_subparsers(dest='automation_command',
919
+ help='Automation operations')
920
+
921
+ # automations add
922
+ auto_add_parser = automation_subparsers.add_parser('add', help='Create a new automation trigger')
923
+ auto_add_parser.add_argument('--name', type=str, required=True, help='Name of the automation')
924
+ auto_add_parser.add_argument('--event-type', type=str, required=True,
925
+ choices=['params', 'weather', 'daylight'],
926
+ help='Event type (params, weather, daylight)')
927
+ auto_add_parser.add_argument('--event', type=str, required=True, action='append',
928
+ help='Event JSON object (repeatable). '
929
+ 'E.g., \'{"params": {"Light": {"Brightness": 100}}, "check": "=="}\'')
930
+ auto_add_parser.add_argument('--action', type=str, required=True,
931
+ help='Action JSON (object or array of objects). '
932
+ 'E.g., \'{"node_id": "xxx", "params": {"Light": {"Output": true}}}\'')
933
+ auto_add_parser.add_argument('--node-id', type=str,
934
+ help='Node ID (required for event-type params)')
935
+ auto_add_parser.add_argument('--location', type=str,
936
+ help='Location as lat,long (required for event-type weather/daylight). '
937
+ 'E.g., 18.521428,73.8544541')
938
+ auto_add_parser.add_argument('--event-operator', type=str, choices=['AND', 'OR'],
939
+ help='Logical operator for multiple events (AND/OR)')
940
+ auto_add_parser.add_argument('--retrigger', action='store_true', default=False,
941
+ help='Allow retriggering for the same event')
942
+ auto_add_parser.add_argument('--metadata', type=str, help='Metadata as JSON string')
943
+ add_profile_argument(auto_add_parser)
944
+ auto_add_parser.set_defaults(func=automation_add)
945
+
946
+ # automations edit
947
+ auto_edit_parser = automation_subparsers.add_parser('edit', help='Edit an existing automation trigger')
948
+ auto_edit_parser.add_argument('--id', type=str, required=True, help='Automation ID')
949
+ auto_edit_parser.add_argument('--name', type=str, help='New name for the automation')
950
+ auto_edit_parser.add_argument('--event', type=str, action='append',
951
+ help='Event JSON object (repeatable)')
952
+ auto_edit_parser.add_argument('--action', type=str,
953
+ help='Action JSON (object or array of objects)')
954
+ auto_edit_parser.add_argument('--node-id', type=str, help='Node ID')
955
+ auto_edit_parser.add_argument('--location', type=str,
956
+ help='Location as lat,long')
957
+ auto_edit_parser.add_argument('--event-operator', type=str, choices=['AND', 'OR'],
958
+ help='Logical operator for multiple events (AND/OR)')
959
+ auto_edit_parser.add_argument('--retrigger', action='store_true', default=False,
960
+ help='Enable retriggering')
961
+ auto_edit_parser.add_argument('--no-retrigger', action='store_true', default=False,
962
+ help='Disable retriggering')
963
+ auto_edit_parser.add_argument('--enabled', action='store_true', default=False,
964
+ help='Enable the automation')
965
+ auto_edit_parser.add_argument('--disabled', action='store_true', default=False,
966
+ help='Disable the automation')
967
+ auto_edit_parser.add_argument('--metadata', type=str, help='Metadata as JSON string')
968
+ add_profile_argument(auto_edit_parser)
969
+ auto_edit_parser.set_defaults(func=automation_edit)
970
+
971
+ # automations remove
972
+ auto_remove_parser = automation_subparsers.add_parser('remove', help='Remove an automation trigger')
973
+ auto_remove_parser.add_argument('--id', type=str, required=True, help='Automation ID to remove')
974
+ add_profile_argument(auto_remove_parser)
975
+ auto_remove_parser.set_defaults(func=automation_remove)
976
+
977
+ # automations get
978
+ auto_get_parser = automation_subparsers.add_parser('get', help='Get automation triggers')
979
+ auto_get_parser.add_argument('--id', type=str, help='Automation ID (omit to list all)')
980
+ auto_get_parser.add_argument('--node-id', type=str, help='Filter by node ID')
981
+ add_profile_argument(auto_get_parser)
982
+ auto_get_parser.set_defaults(func=automation_get)
983
+
861
984
  # Node Management (tags, metadata)
862
985
  node_parser = subparsers.add_parser('node',
863
986
  help='Manage node properties (tags, metadata)')
@@ -967,6 +1090,31 @@ def main():
967
1090
  add_profile_argument(raw_api_parser)
968
1091
  raw_api_parser.set_defaults(func=raw_api_call)
969
1092
 
1093
+ # Video Streaming
1094
+ stream_parser = subparsers.add_parser('stream',
1095
+ help='Stream video from a camera device')
1096
+ stream_parser.add_argument('nodeid',
1097
+ type=str,
1098
+ metavar='<nodeid>',
1099
+ help='Node ID for the camera device')
1100
+ stream_parser.add_argument('--output', '-o',
1101
+ type=str,
1102
+ metavar='<path>',
1103
+ help='Save video to file (MP4)')
1104
+ stream_parser.add_argument('--region',
1105
+ type=str,
1106
+ metavar='<region>',
1107
+ help='AWS region override')
1108
+ stream_parser.add_argument('--duration',
1109
+ type=int,
1110
+ metavar='<seconds>',
1111
+ help='Stream duration in seconds')
1112
+ stream_parser.add_argument('--stats-only',
1113
+ action='store_true',
1114
+ help='Show statistics only, no video display')
1115
+ add_profile_argument(stream_parser)
1116
+ stream_parser.set_defaults(func=stream_video)
1117
+
970
1118
  args = parser.parse_args()
971
1119
 
972
1120
  if args.func is not None:
@@ -5,4 +5,4 @@
5
5
  # SPDX-License-Identifier: Apache-2.0
6
6
 
7
7
  # This file contains the version information for the ESP RainMaker CLI
8
- VERSION = "1.11.1"
8
+ VERSION = "1.13.0"
@@ -0,0 +1,217 @@
1
+ # SPDX-FileCopyrightText: 2020-2025 Espressif Systems (Shanghai) CO LTD
2
+ #
3
+ # SPDX-License-Identifier: Apache-2.0
4
+
5
+ from rmaker_lib.profile_utils import get_session_with_profile
6
+ import json
7
+
8
+
9
+ def _handle_error(e, operation):
10
+ """Helper function to handle errors and extract descriptions from API responses"""
11
+ error_msg = str(e)
12
+ try:
13
+ if hasattr(e, 'response') and e.response is not None:
14
+ resp = e.response.json()
15
+ if 'description' in resp:
16
+ error_msg = resp['description']
17
+ except Exception:
18
+ pass
19
+ print(f"Failed to {operation}: {error_msg}")
20
+
21
+
22
+ def _parse_json(value, field_name):
23
+ """Parse a JSON string, returning the parsed object or None on error."""
24
+ try:
25
+ return json.loads(value)
26
+ except Exception as e:
27
+ print(f"Invalid JSON for {field_name}: {e}")
28
+ return None
29
+
30
+
31
+ def _parse_location(location_str):
32
+ """Parse 'lat,long' string into location dict."""
33
+ parts = location_str.split(',')
34
+ if len(parts) != 2:
35
+ print("Invalid location format. Use: lat,long (e.g., 18.521428,73.8544541)")
36
+ return None
37
+ return {"latitude": parts[0].strip(), "longitude": parts[1].strip()}
38
+
39
+
40
+ def automation_add(vars=None):
41
+ try:
42
+ s = get_session_with_profile(vars or {})
43
+ name = vars['name']
44
+ event_type = vars['event_type']
45
+ event_jsons = vars['event']
46
+ action_json = vars['action']
47
+ node_id = vars.get('node_id')
48
+ location = vars.get('location')
49
+ event_operator = vars.get('event_operator')
50
+ retrigger = vars.get('retrigger', False)
51
+ metadata_arg = vars.get('metadata')
52
+
53
+ # Map 'params' to 'node_params' for the API
54
+ api_event_type = 'node_params' if event_type == 'params' else event_type
55
+
56
+ # Validate event_type-specific requirements
57
+ if event_type == 'params' and not node_id:
58
+ print("Error: --node-id is required when --event-type is 'params'")
59
+ return
60
+ if event_type in ('weather', 'daylight') and not location:
61
+ print(f"Error: --location is required when --event-type is '{event_type}'")
62
+ return
63
+
64
+ # Parse events
65
+ events = []
66
+ for ev in event_jsons:
67
+ parsed = _parse_json(ev, 'event')
68
+ if parsed is None:
69
+ return
70
+ events.append(parsed)
71
+
72
+ # Validate event_operator if multiple events
73
+ if len(events) > 1 and not event_operator:
74
+ print("Error: --event-operator (AND/OR) is required when multiple events are specified")
75
+ return
76
+
77
+ # Parse action
78
+ action = _parse_json(action_json, 'action')
79
+ if action is None:
80
+ return
81
+ if isinstance(action, dict):
82
+ actions = [action]
83
+ elif isinstance(action, list):
84
+ actions = action
85
+ else:
86
+ print("Error: --action must be a JSON object or array of objects")
87
+ return
88
+
89
+ # Build payload
90
+ payload = {
91
+ 'name': name,
92
+ 'event_type': api_event_type,
93
+ 'events': events,
94
+ 'actions': actions,
95
+ 'retrigger': retrigger,
96
+ }
97
+ if node_id:
98
+ payload['node_id'] = node_id
99
+ if location:
100
+ loc = _parse_location(location)
101
+ if loc is None:
102
+ return
103
+ payload['location'] = loc
104
+ if event_operator:
105
+ payload['event_operator'] = event_operator.lower()
106
+ if metadata_arg:
107
+ metadata = _parse_json(metadata_arg, 'metadata')
108
+ if metadata is None:
109
+ return
110
+ payload['metadata'] = metadata
111
+
112
+ resp = s.create_automation(payload)
113
+ print("Automation created successfully:")
114
+ print(json.dumps(resp, indent=2))
115
+ except Exception as e:
116
+ _handle_error(e, "create automation")
117
+
118
+
119
+ def automation_edit(vars=None):
120
+ try:
121
+ s = get_session_with_profile(vars or {})
122
+ automation_id = vars['id']
123
+
124
+ payload = {}
125
+ if vars.get('name'):
126
+ payload['name'] = vars['name']
127
+ if vars.get('node_id'):
128
+ payload['node_id'] = vars['node_id']
129
+ if vars.get('event'):
130
+ events = []
131
+ for ev in vars['event']:
132
+ parsed = _parse_json(ev, 'event')
133
+ if parsed is None:
134
+ return
135
+ events.append(parsed)
136
+ payload['events'] = events
137
+ if vars.get('action'):
138
+ action = _parse_json(vars['action'], 'action')
139
+ if action is None:
140
+ return
141
+ if isinstance(action, dict):
142
+ payload['actions'] = [action]
143
+ elif isinstance(action, list):
144
+ payload['actions'] = action
145
+ else:
146
+ print("Error: --action must be a JSON object or array of objects")
147
+ return
148
+ if vars.get('event_operator'):
149
+ payload['event_operator'] = vars['event_operator'].lower()
150
+ if vars.get('location'):
151
+ loc = _parse_location(vars['location'])
152
+ if loc is None:
153
+ return
154
+ payload['location'] = loc
155
+ if vars.get('metadata'):
156
+ metadata = _parse_json(vars['metadata'], 'metadata')
157
+ if metadata is None:
158
+ return
159
+ payload['metadata'] = metadata
160
+
161
+ # Handle retrigger/no-retrigger
162
+ if vars.get('retrigger'):
163
+ payload['retrigger'] = True
164
+ elif vars.get('no_retrigger'):
165
+ payload['retrigger'] = False
166
+
167
+ # Handle enabled/disabled
168
+ if vars.get('enabled'):
169
+ payload['enabled'] = True
170
+ elif vars.get('disabled'):
171
+ payload['enabled'] = False
172
+
173
+ if not payload:
174
+ print("No fields to update. Provide at least one option to edit.")
175
+ return
176
+
177
+ resp = s.update_automation(automation_id, payload)
178
+ print("Automation updated successfully:")
179
+ print(json.dumps(resp, indent=2))
180
+ except Exception as e:
181
+ _handle_error(e, "update automation")
182
+
183
+
184
+ def automation_remove(vars=None):
185
+ try:
186
+ s = get_session_with_profile(vars or {})
187
+ automation_id = vars['id']
188
+ resp = s.remove_automation(automation_id)
189
+ print("Automation removed successfully:")
190
+ print(json.dumps(resp, indent=2))
191
+ except Exception as e:
192
+ _handle_error(e, "remove automation")
193
+
194
+
195
+ def automation_get(vars=None):
196
+ try:
197
+ s = get_session_with_profile(vars or {})
198
+ automation_id = vars.get('id')
199
+ node_id = vars.get('node_id')
200
+ resp = s.get_automations(automation_id=automation_id, node_id=node_id)
201
+
202
+ if not resp:
203
+ print("No automations found.")
204
+ return
205
+
206
+ automations = resp.get('automation_trigger_actions', [])
207
+ if not automations:
208
+ print("No automations found.")
209
+ return
210
+
211
+ if automation_id:
212
+ print("Automation details:")
213
+ else:
214
+ print(f"Automations ({len(automations)}):")
215
+ print(json.dumps(automations, indent=2))
216
+ except Exception as e:
217
+ _handle_error(e, "get automations")