esp-rainmaker-cli 1.13.1__tar.gz → 1.14.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 (126) hide show
  1. {esp_rainmaker_cli-1.13.1 → esp_rainmaker_cli-1.14.0}/PKG-INFO +8 -1
  2. {esp_rainmaker_cli-1.13.1 → esp_rainmaker_cli-1.14.0}/esp_rainmaker_cli.egg-info/PKG-INFO +8 -1
  3. {esp_rainmaker_cli-1.13.1 → esp_rainmaker_cli-1.14.0}/rainmaker/rainmaker.py +127 -1
  4. {esp_rainmaker_cli-1.13.1 → esp_rainmaker_cli-1.14.0}/rainmaker/version.py +1 -1
  5. {esp_rainmaker_cli-1.13.1 → esp_rainmaker_cli-1.14.0}/rmaker_cmd/group.py +218 -2
  6. {esp_rainmaker_cli-1.13.1 → esp_rainmaker_cli-1.14.0}/rmaker_lib/session.py +148 -0
  7. {esp_rainmaker_cli-1.13.1 → esp_rainmaker_cli-1.14.0}/LICENSE +0 -0
  8. {esp_rainmaker_cli-1.13.1 → esp_rainmaker_cli-1.14.0}/README.md +0 -0
  9. {esp_rainmaker_cli-1.13.1 → esp_rainmaker_cli-1.14.0}/esp_rainmaker_cli.egg-info/SOURCES.txt +0 -0
  10. {esp_rainmaker_cli-1.13.1 → esp_rainmaker_cli-1.14.0}/esp_rainmaker_cli.egg-info/dependency_links.txt +0 -0
  11. {esp_rainmaker_cli-1.13.1 → esp_rainmaker_cli-1.14.0}/esp_rainmaker_cli.egg-info/entry_points.txt +0 -0
  12. {esp_rainmaker_cli-1.13.1 → esp_rainmaker_cli-1.14.0}/esp_rainmaker_cli.egg-info/requires.txt +0 -0
  13. {esp_rainmaker_cli-1.13.1 → esp_rainmaker_cli-1.14.0}/esp_rainmaker_cli.egg-info/top_level.txt +0 -0
  14. {esp_rainmaker_cli-1.13.1 → esp_rainmaker_cli-1.14.0}/rainmaker/__init__.py +0 -0
  15. {esp_rainmaker_cli-1.13.1 → esp_rainmaker_cli-1.14.0}/rmaker_cmd/__init__.py +0 -0
  16. {esp_rainmaker_cli-1.13.1 → esp_rainmaker_cli-1.14.0}/rmaker_cmd/automation.py +0 -0
  17. {esp_rainmaker_cli-1.13.1 → esp_rainmaker_cli-1.14.0}/rmaker_cmd/browserlogin.py +0 -0
  18. {esp_rainmaker_cli-1.13.1 → esp_rainmaker_cli-1.14.0}/rmaker_cmd/cache.py +0 -0
  19. {esp_rainmaker_cli-1.13.1 → esp_rainmaker_cli-1.14.0}/rmaker_cmd/cmd_response.py +0 -0
  20. {esp_rainmaker_cli-1.13.1 → esp_rainmaker_cli-1.14.0}/rmaker_cmd/html/welcome.html +0 -0
  21. {esp_rainmaker_cli-1.13.1 → esp_rainmaker_cli-1.14.0}/rmaker_cmd/node.py +0 -0
  22. {esp_rainmaker_cli-1.13.1 → esp_rainmaker_cli-1.14.0}/rmaker_cmd/provision.py +0 -0
  23. {esp_rainmaker_cli-1.13.1 → esp_rainmaker_cli-1.14.0}/rmaker_cmd/stream.py +0 -0
  24. {esp_rainmaker_cli-1.13.1 → esp_rainmaker_cli-1.14.0}/rmaker_cmd/test.py +0 -0
  25. {esp_rainmaker_cli-1.13.1 → esp_rainmaker_cli-1.14.0}/rmaker_cmd/user.py +0 -0
  26. {esp_rainmaker_cli-1.13.1 → esp_rainmaker_cli-1.14.0}/rmaker_lib/__init__.py +0 -0
  27. {esp_rainmaker_cli-1.13.1 → esp_rainmaker_cli-1.14.0}/rmaker_lib/aws_credentials.py +0 -0
  28. {esp_rainmaker_cli-1.13.1 → esp_rainmaker_cli-1.14.0}/rmaker_lib/cmd_response.py +0 -0
  29. {esp_rainmaker_cli-1.13.1 → esp_rainmaker_cli-1.14.0}/rmaker_lib/configmanager.py +0 -0
  30. {esp_rainmaker_cli-1.13.1 → esp_rainmaker_cli-1.14.0}/rmaker_lib/constants.py +0 -0
  31. {esp_rainmaker_cli-1.13.1 → esp_rainmaker_cli-1.14.0}/rmaker_lib/device.py +0 -0
  32. {esp_rainmaker_cli-1.13.1 → esp_rainmaker_cli-1.14.0}/rmaker_lib/envval.py +0 -0
  33. {esp_rainmaker_cli-1.13.1 → esp_rainmaker_cli-1.14.0}/rmaker_lib/exceptions.py +0 -0
  34. {esp_rainmaker_cli-1.13.1 → esp_rainmaker_cli-1.14.0}/rmaker_lib/kvs_streaming.py +0 -0
  35. {esp_rainmaker_cli-1.13.1 → esp_rainmaker_cli-1.14.0}/rmaker_lib/local_control.py +0 -0
  36. {esp_rainmaker_cli-1.13.1 → esp_rainmaker_cli-1.14.0}/rmaker_lib/logger.py +0 -0
  37. {esp_rainmaker_cli-1.13.1 → esp_rainmaker_cli-1.14.0}/rmaker_lib/node.py +0 -0
  38. {esp_rainmaker_cli-1.13.1 → esp_rainmaker_cli-1.14.0}/rmaker_lib/node_cache.py +0 -0
  39. {esp_rainmaker_cli-1.13.1 → esp_rainmaker_cli-1.14.0}/rmaker_lib/profile_manager.py +0 -0
  40. {esp_rainmaker_cli-1.13.1 → esp_rainmaker_cli-1.14.0}/rmaker_lib/profile_utils.py +0 -0
  41. {esp_rainmaker_cli-1.13.1 → esp_rainmaker_cli-1.14.0}/rmaker_lib/schedule_utils.py +0 -0
  42. {esp_rainmaker_cli-1.13.1 → esp_rainmaker_cli-1.14.0}/rmaker_lib/serverconfig.py +0 -0
  43. {esp_rainmaker_cli-1.13.1 → esp_rainmaker_cli-1.14.0}/rmaker_lib/service.py +0 -0
  44. {esp_rainmaker_cli-1.13.1 → esp_rainmaker_cli-1.14.0}/rmaker_lib/session_store.py +0 -0
  45. {esp_rainmaker_cli-1.13.1 → esp_rainmaker_cli-1.14.0}/rmaker_lib/simple_local_control.py +0 -0
  46. {esp_rainmaker_cli-1.13.1 → esp_rainmaker_cli-1.14.0}/rmaker_lib/user.py +0 -0
  47. {esp_rainmaker_cli-1.13.1 → esp_rainmaker_cli-1.14.0}/rmaker_tools/__init__.py +0 -0
  48. {esp_rainmaker_cli-1.13.1 → esp_rainmaker_cli-1.14.0}/rmaker_tools/common/__init__.py +0 -0
  49. {esp_rainmaker_cli-1.13.1 → esp_rainmaker_cli-1.14.0}/rmaker_tools/common/discovery/__init__.py +0 -0
  50. {esp_rainmaker_cli-1.13.1 → esp_rainmaker_cli-1.14.0}/rmaker_tools/common/discovery/mdns_discovery.py +0 -0
  51. {esp_rainmaker_cli-1.13.1 → esp_rainmaker_cli-1.14.0}/rmaker_tools/common/prov/__init__.py +0 -0
  52. {esp_rainmaker_cli-1.13.1 → esp_rainmaker_cli-1.14.0}/rmaker_tools/common/prov/wifi_ctrl.py +0 -0
  53. {esp_rainmaker_cli-1.13.1 → esp_rainmaker_cli-1.14.0}/rmaker_tools/common/prov/wifi_prov.py +0 -0
  54. {esp_rainmaker_cli-1.13.1 → esp_rainmaker_cli-1.14.0}/rmaker_tools/common/prov/wifi_scan.py +0 -0
  55. {esp_rainmaker_cli-1.13.1 → esp_rainmaker_cli-1.14.0}/rmaker_tools/common/security/__init__.py +0 -0
  56. {esp_rainmaker_cli-1.13.1 → esp_rainmaker_cli-1.14.0}/rmaker_tools/common/security/security.py +0 -0
  57. {esp_rainmaker_cli-1.13.1 → esp_rainmaker_cli-1.14.0}/rmaker_tools/common/security/security0.py +0 -0
  58. {esp_rainmaker_cli-1.13.1 → esp_rainmaker_cli-1.14.0}/rmaker_tools/common/security/security1.py +0 -0
  59. {esp_rainmaker_cli-1.13.1 → esp_rainmaker_cli-1.14.0}/rmaker_tools/common/security/security2.py +0 -0
  60. {esp_rainmaker_cli-1.13.1 → esp_rainmaker_cli-1.14.0}/rmaker_tools/common/security/srp6a.py +0 -0
  61. {esp_rainmaker_cli-1.13.1 → esp_rainmaker_cli-1.14.0}/rmaker_tools/common/transport/__init__.py +0 -0
  62. {esp_rainmaker_cli-1.13.1 → esp_rainmaker_cli-1.14.0}/rmaker_tools/common/transport/ble_cli.py +0 -0
  63. {esp_rainmaker_cli-1.13.1 → esp_rainmaker_cli-1.14.0}/rmaker_tools/common/transport/transport.py +0 -0
  64. {esp_rainmaker_cli-1.13.1 → esp_rainmaker_cli-1.14.0}/rmaker_tools/common/transport/transport_ble.py +0 -0
  65. {esp_rainmaker_cli-1.13.1 → esp_rainmaker_cli-1.14.0}/rmaker_tools/common/transport/transport_console.py +0 -0
  66. {esp_rainmaker_cli-1.13.1 → esp_rainmaker_cli-1.14.0}/rmaker_tools/common/transport/transport_http.py +0 -0
  67. {esp_rainmaker_cli-1.13.1 → esp_rainmaker_cli-1.14.0}/rmaker_tools/common/utils/__init__.py +0 -0
  68. {esp_rainmaker_cli-1.13.1 → esp_rainmaker_cli-1.14.0}/rmaker_tools/common/utils/convenience.py +0 -0
  69. {esp_rainmaker_cli-1.13.1 → esp_rainmaker_cli-1.14.0}/rmaker_tools/rmaker_claim/__init__.py +0 -0
  70. {esp_rainmaker_cli-1.13.1 → esp_rainmaker_cli-1.14.0}/rmaker_tools/rmaker_claim/claim.py +0 -0
  71. {esp_rainmaker_cli-1.13.1 → esp_rainmaker_cli-1.14.0}/rmaker_tools/rmaker_claim/claim_config.py +0 -0
  72. {esp_rainmaker_cli-1.13.1 → esp_rainmaker_cli-1.14.0}/rmaker_tools/rmaker_local_ctrl/__init__.py +0 -0
  73. {esp_rainmaker_cli-1.13.1 → esp_rainmaker_cli-1.14.0}/rmaker_tools/rmaker_local_ctrl/constants_pb2.py +0 -0
  74. {esp_rainmaker_cli-1.13.1 → esp_rainmaker_cli-1.14.0}/rmaker_tools/rmaker_local_ctrl/esp_local_ctrl.py +0 -0
  75. {esp_rainmaker_cli-1.13.1 → esp_rainmaker_cli-1.14.0}/rmaker_tools/rmaker_local_ctrl/esp_local_ctrl_pb2.py +0 -0
  76. {esp_rainmaker_cli-1.13.1 → esp_rainmaker_cli-1.14.0}/rmaker_tools/rmaker_local_ctrl/esp_prov.py +0 -0
  77. {esp_rainmaker_cli-1.13.1 → esp_rainmaker_cli-1.14.0}/rmaker_tools/rmaker_local_ctrl/esp_rainmaker_ctrl.py +0 -0
  78. {esp_rainmaker_cli-1.13.1 → esp_rainmaker_cli-1.14.0}/rmaker_tools/rmaker_local_ctrl/esp_rmaker_prov_local_ctrl_pb2.py +0 -0
  79. {esp_rainmaker_cli-1.13.1 → esp_rainmaker_cli-1.14.0}/rmaker_tools/rmaker_local_ctrl/integration.py +0 -0
  80. {esp_rainmaker_cli-1.13.1 → esp_rainmaker_cli-1.14.0}/rmaker_tools/rmaker_local_ctrl/proto/__init__.py +0 -0
  81. {esp_rainmaker_cli-1.13.1 → esp_rainmaker_cli-1.14.0}/rmaker_tools/rmaker_local_ctrl/proto/proto_lc.py +0 -0
  82. {esp_rainmaker_cli-1.13.1 → esp_rainmaker_cli-1.14.0}/rmaker_tools/rmaker_local_ctrl/prov/__init__.py +0 -0
  83. {esp_rainmaker_cli-1.13.1 → esp_rainmaker_cli-1.14.0}/rmaker_tools/rmaker_local_ctrl/prov/custom_prov.py +0 -0
  84. {esp_rainmaker_cli-1.13.1 → esp_rainmaker_cli-1.14.0}/rmaker_tools/rmaker_local_ctrl/prov/wifi_ctrl.py +0 -0
  85. {esp_rainmaker_cli-1.13.1 → esp_rainmaker_cli-1.14.0}/rmaker_tools/rmaker_local_ctrl/raw_config.py +0 -0
  86. {esp_rainmaker_cli-1.13.1 → esp_rainmaker_cli-1.14.0}/rmaker_tools/rmaker_local_ctrl/raw_params.py +0 -0
  87. {esp_rainmaker_cli-1.13.1 → esp_rainmaker_cli-1.14.0}/rmaker_tools/rmaker_local_ctrl/sec0_pb2.py +0 -0
  88. {esp_rainmaker_cli-1.13.1 → esp_rainmaker_cli-1.14.0}/rmaker_tools/rmaker_local_ctrl/sec1_pb2.py +0 -0
  89. {esp_rainmaker_cli-1.13.1 → esp_rainmaker_cli-1.14.0}/rmaker_tools/rmaker_local_ctrl/sec2_pb2.py +0 -0
  90. {esp_rainmaker_cli-1.13.1 → esp_rainmaker_cli-1.14.0}/rmaker_tools/rmaker_local_ctrl/security/__init__.py +0 -0
  91. {esp_rainmaker_cli-1.13.1 → esp_rainmaker_cli-1.14.0}/rmaker_tools/rmaker_local_ctrl/session_pb2.py +0 -0
  92. {esp_rainmaker_cli-1.13.1 → esp_rainmaker_cli-1.14.0}/rmaker_tools/rmaker_local_ctrl/transport/__init__.py +0 -0
  93. {esp_rainmaker_cli-1.13.1 → esp_rainmaker_cli-1.14.0}/rmaker_tools/rmaker_local_ctrl/utils/__init__.py +0 -0
  94. {esp_rainmaker_cli-1.13.1 → esp_rainmaker_cli-1.14.0}/rmaker_tools/rmaker_prov/__init__.py +0 -0
  95. {esp_rainmaker_cli-1.13.1 → esp_rainmaker_cli-1.14.0}/rmaker_tools/rmaker_prov/challenge_response.py +0 -0
  96. {esp_rainmaker_cli-1.13.1 → esp_rainmaker_cli-1.14.0}/rmaker_tools/rmaker_prov/config/__init__.py +0 -0
  97. {esp_rainmaker_cli-1.13.1 → esp_rainmaker_cli-1.14.0}/rmaker_tools/rmaker_prov/config/custom_cloud_config_pb2.py +0 -0
  98. {esp_rainmaker_cli-1.13.1 → esp_rainmaker_cli-1.14.0}/rmaker_tools/rmaker_prov/config/esp_rmaker_chal_resp_pb2.py +0 -0
  99. {esp_rainmaker_cli-1.13.1 → esp_rainmaker_cli-1.14.0}/rmaker_tools/rmaker_prov/config/esp_rmaker_claim_pb2.py +0 -0
  100. {esp_rainmaker_cli-1.13.1 → esp_rainmaker_cli-1.14.0}/rmaker_tools/rmaker_prov/config/esp_rmaker_user_mapping_pb2.py +0 -0
  101. {esp_rainmaker_cli-1.13.1 → esp_rainmaker_cli-1.14.0}/rmaker_tools/rmaker_prov/esp_rainmaker_prov.py +0 -0
  102. {esp_rainmaker_cli-1.13.1 → esp_rainmaker_cli-1.14.0}/rmaker_tools/rmaker_prov/on_network_chal_resp.py +0 -0
  103. {esp_rainmaker_cli-1.13.1 → esp_rainmaker_cli-1.14.0}/rmaker_tools/rmaker_prov/proto/__init__.py +0 -0
  104. {esp_rainmaker_cli-1.13.1 → esp_rainmaker_cli-1.14.0}/rmaker_tools/rmaker_prov/protocomm/__init__.py +0 -0
  105. {esp_rainmaker_cli-1.13.1 → esp_rainmaker_cli-1.14.0}/rmaker_tools/rmaker_prov/protocomm/python/__init__.py +0 -0
  106. {esp_rainmaker_cli-1.13.1 → esp_rainmaker_cli-1.14.0}/rmaker_tools/rmaker_prov/protocomm/python/constants_pb2.py +0 -0
  107. {esp_rainmaker_cli-1.13.1 → esp_rainmaker_cli-1.14.0}/rmaker_tools/rmaker_prov/protocomm/python/sec0_pb2.py +0 -0
  108. {esp_rainmaker_cli-1.13.1 → esp_rainmaker_cli-1.14.0}/rmaker_tools/rmaker_prov/protocomm/python/sec1_pb2.py +0 -0
  109. {esp_rainmaker_cli-1.13.1 → esp_rainmaker_cli-1.14.0}/rmaker_tools/rmaker_prov/protocomm/python/sec2_pb2.py +0 -0
  110. {esp_rainmaker_cli-1.13.1 → esp_rainmaker_cli-1.14.0}/rmaker_tools/rmaker_prov/protocomm/python/session_pb2.py +0 -0
  111. {esp_rainmaker_cli-1.13.1 → esp_rainmaker_cli-1.14.0}/rmaker_tools/rmaker_prov/prov/__init__.py +0 -0
  112. {esp_rainmaker_cli-1.13.1 → esp_rainmaker_cli-1.14.0}/rmaker_tools/rmaker_prov/prov/prov_util.py +0 -0
  113. {esp_rainmaker_cli-1.13.1 → esp_rainmaker_cli-1.14.0}/rmaker_tools/rmaker_prov/prov/user_mapping.py +0 -0
  114. {esp_rainmaker_cli-1.13.1 → esp_rainmaker_cli-1.14.0}/rmaker_tools/rmaker_prov/security/__init__.py +0 -0
  115. {esp_rainmaker_cli-1.13.1 → esp_rainmaker_cli-1.14.0}/rmaker_tools/rmaker_prov/transport/__init__.py +0 -0
  116. {esp_rainmaker_cli-1.13.1 → esp_rainmaker_cli-1.14.0}/rmaker_tools/rmaker_prov/utils/__init__.py +0 -0
  117. {esp_rainmaker_cli-1.13.1 → esp_rainmaker_cli-1.14.0}/rmaker_tools/rmaker_prov/wifi_provisioning/__init__.py +0 -0
  118. {esp_rainmaker_cli-1.13.1 → esp_rainmaker_cli-1.14.0}/rmaker_tools/rmaker_prov/wifi_provisioning/python/__init__.py +0 -0
  119. {esp_rainmaker_cli-1.13.1 → esp_rainmaker_cli-1.14.0}/rmaker_tools/rmaker_prov/wifi_provisioning/python/wifi_config_pb2.py +0 -0
  120. {esp_rainmaker_cli-1.13.1 → esp_rainmaker_cli-1.14.0}/rmaker_tools/rmaker_prov/wifi_provisioning/python/wifi_constants_pb2.py +0 -0
  121. {esp_rainmaker_cli-1.13.1 → esp_rainmaker_cli-1.14.0}/rmaker_tools/rmaker_prov/wifi_provisioning/python/wifi_ctrl_pb2.py +0 -0
  122. {esp_rainmaker_cli-1.13.1 → esp_rainmaker_cli-1.14.0}/rmaker_tools/rmaker_prov/wifi_provisioning/python/wifi_scan_pb2.py +0 -0
  123. {esp_rainmaker_cli-1.13.1 → esp_rainmaker_cli-1.14.0}/server_cert/__init__.py +0 -0
  124. {esp_rainmaker_cli-1.13.1 → esp_rainmaker_cli-1.14.0}/server_cert/server_cert.pem +0 -0
  125. {esp_rainmaker_cli-1.13.1 → esp_rainmaker_cli-1.14.0}/setup.cfg +0 -0
  126. {esp_rainmaker_cli-1.13.1 → esp_rainmaker_cli-1.14.0}/setup.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: esp-rainmaker-cli
3
- Version: 1.13.1
3
+ Version: 1.14.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
@@ -122,6 +122,13 @@ Changelog
122
122
 
123
123
  All major changes to ESP RainMaker CLI will be documented in this file.
124
124
 
125
+ ## [1.14.0] - 24-Apr-2026
126
+ ### Added
127
+ - New `group sharing` subcommand for sharing device groups / Matter fabrics between users:
128
+ - `add`, `remove`, `list`, `list-requests`, `accept`, `decline`, `cancel` operations
129
+ - Supports primary/secondary roles, sub-roles (1-4), metadata, and ownership transfer (`--transfer`, `--new-role`)
130
+ - Parsed, human-readable output by default; pass `--raw` on any operation to get the underlying JSON response
131
+
125
132
  ## [1.13.1] - 23-Apr-2026
126
133
  ### Added
127
134
  - Node sharing enhancements:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: esp-rainmaker-cli
3
- Version: 1.13.1
3
+ Version: 1.14.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
@@ -122,6 +122,13 @@ Changelog
122
122
 
123
123
  All major changes to ESP RainMaker CLI will be documented in this file.
124
124
 
125
+ ## [1.14.0] - 24-Apr-2026
126
+ ### Added
127
+ - New `group sharing` subcommand for sharing device groups / Matter fabrics between users:
128
+ - `add`, `remove`, `list`, `list-requests`, `accept`, `decline`, `cancel` operations
129
+ - Supports primary/secondary roles, sub-roles (1-4), metadata, and ownership transfer (`--transfer`, `--new-role`)
130
+ - Parsed, human-readable output by default; pass `--raw` on any operation to get the underlying JSON response
131
+
125
132
  ## [1.13.1] - 23-Apr-2026
126
133
  ### Added
127
134
  - Node sharing enhancements:
@@ -15,7 +15,12 @@ from rmaker_cmd.cmd_response import get_cmd_requests, create_cmd_request
15
15
  from rmaker_cmd.provision import provision
16
16
  from rmaker_cmd.test import test
17
17
  from rmaker_lib.logger import log
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
18
+ from rmaker_cmd.group import (group_add, group_remove, group_edit, group_list,
19
+ group_show, group_add_nodes, group_remove_nodes,
20
+ group_list_nodes, group_sharing_add,
21
+ group_sharing_remove, group_sharing_list,
22
+ group_sharing_list_requests, group_sharing_accept,
23
+ group_sharing_decline, group_sharing_cancel)
19
24
  from rmaker_cmd.automation import automation_add, automation_edit, automation_remove, automation_get
20
25
  from rmaker_cmd.cache import cache_manage
21
26
  from rmaker_cmd.stream import stream_video
@@ -934,6 +939,127 @@ def main():
934
939
  add_profile_argument(group_list_nodes_parser)
935
940
  group_list_nodes_parser.set_defaults(func=group_list_nodes)
936
941
 
942
+ # group sharing
943
+ group_sharing_parser = group_subparsers.add_parser(
944
+ 'sharing',
945
+ help='Share groups / matter fabrics between users',
946
+ formatter_class=argparse.RawTextHelpFormatter,
947
+ description='Share groups or matter fabrics with other users.\n'
948
+ 'Primary users can share groups; secondary users can view '
949
+ 'their sharing or leave groups they have access to.')
950
+ group_sharing_parser.set_defaults(
951
+ func=lambda vars=None: group_sharing_parser.print_help())
952
+ group_sharing_subparsers = group_sharing_parser.add_subparsers(
953
+ dest='sharing_command', help='Group sharing operations')
954
+
955
+ # group sharing add
956
+ gs_add_parser = group_sharing_subparsers.add_parser(
957
+ 'add', help='Share group(s) with a user (or add self-sharing without --user)',
958
+ formatter_class=argparse.RawTextHelpFormatter)
959
+ gs_add_parser.add_argument('--groups', type=str, required=True,
960
+ metavar='<group_ids>',
961
+ help='Comma separated group ids (max 10)')
962
+ gs_add_parser.add_argument('--user', type=str, metavar='<user_name>',
963
+ help='User name (email) to share with. Optional.')
964
+ gs_add_parser.add_argument('--primary', action='store_true',
965
+ help='Share with primary access (default: secondary)')
966
+ gs_add_parser.add_argument('--sub-role', type=int, choices=range(1, 5),
967
+ metavar='<1-4>',
968
+ help='Custom sub role (1-4)')
969
+ gs_add_parser.add_argument('--metadata', type=str, metavar='<json>',
970
+ help='Custom metadata as a JSON string')
971
+ gs_add_parser.add_argument('--transfer', action='store_true',
972
+ help='Transfer ownership of the group(s) to the user')
973
+ gs_add_parser.add_argument('--new-role', type=str, choices=['secondary'],
974
+ help='Role to assign to self after transfer '
975
+ '(only valid with --transfer)')
976
+ gs_add_parser.add_argument('--raw', action='store_true',
977
+ help='Print the raw JSON response instead of parsed output')
978
+ add_profile_argument(gs_add_parser)
979
+ gs_add_parser.set_defaults(func=group_sharing_add)
980
+
981
+ # group sharing remove
982
+ gs_remove_parser = group_sharing_subparsers.add_parser(
983
+ 'remove', help='Remove group sharing with a user',
984
+ formatter_class=argparse.RawTextHelpFormatter)
985
+ gs_remove_parser.add_argument('--groups', type=str, required=True,
986
+ metavar='<group_ids>',
987
+ help='Comma separated group ids')
988
+ gs_remove_parser.add_argument('--user', type=str, required=True,
989
+ metavar='<user_name>',
990
+ help='User name (email) to unshare with')
991
+ gs_remove_parser.add_argument('--raw', action='store_true',
992
+ help='Print the raw JSON response instead of parsed output')
993
+ add_profile_argument(gs_remove_parser)
994
+ gs_remove_parser.set_defaults(func=group_sharing_remove)
995
+
996
+ # group sharing list
997
+ gs_list_parser = group_sharing_subparsers.add_parser(
998
+ 'list', help='Show sharing details for groups / matter fabrics',
999
+ formatter_class=argparse.RawTextHelpFormatter)
1000
+ gs_list_parser.add_argument('--group-id', type=str, metavar='<group_id>',
1001
+ help='Fetch sharing details for a single group')
1002
+ gs_list_parser.add_argument('--sub-groups', action='store_true',
1003
+ help='Include sharing details of sub-groups')
1004
+ gs_list_parser.add_argument('--parent-groups', action='store_true',
1005
+ help='Include sharing details of parent groups')
1006
+ gs_list_parser.add_argument('--metadata', action='store_true',
1007
+ help='Include metadata set during sharing')
1008
+ gs_list_parser.add_argument('--raw', action='store_true',
1009
+ help='Print the raw JSON response instead of parsed output')
1010
+ add_profile_argument(gs_list_parser)
1011
+ gs_list_parser.set_defaults(func=group_sharing_list)
1012
+
1013
+ # group sharing list-requests
1014
+ gs_list_req_parser = group_sharing_subparsers.add_parser(
1015
+ 'list-requests', help='List pending group sharing requests',
1016
+ formatter_class=argparse.RawTextHelpFormatter,
1017
+ description='List pending group sharing requests.\n'
1018
+ 'Use --primary-user to list requests raised by you.\n'
1019
+ 'Without the flag, lists requests you have received.')
1020
+ gs_list_req_parser.add_argument('--id', type=str, metavar='<request_id>',
1021
+ help='Fetch a specific request by id')
1022
+ gs_list_req_parser.add_argument('--primary-user', action='store_true',
1023
+ help='List requests raised by current user '
1024
+ '(default: requests received)')
1025
+ gs_list_req_parser.add_argument('--raw', action='store_true',
1026
+ help='Print the raw JSON response instead of parsed output')
1027
+ add_profile_argument(gs_list_req_parser)
1028
+ gs_list_req_parser.set_defaults(func=group_sharing_list_requests)
1029
+
1030
+ # group sharing accept
1031
+ gs_accept_parser = group_sharing_subparsers.add_parser(
1032
+ 'accept', help='Accept a pending group sharing request')
1033
+ gs_accept_parser.add_argument('--id', type=str, required=True,
1034
+ metavar='<request_id>',
1035
+ help='Id of the sharing request')
1036
+ gs_accept_parser.add_argument('--raw', action='store_true',
1037
+ help='Print the raw JSON response instead of parsed output')
1038
+ add_profile_argument(gs_accept_parser)
1039
+ gs_accept_parser.set_defaults(func=group_sharing_accept)
1040
+
1041
+ # group sharing decline
1042
+ gs_decline_parser = group_sharing_subparsers.add_parser(
1043
+ 'decline', help='Decline a pending group sharing request')
1044
+ gs_decline_parser.add_argument('--id', type=str, required=True,
1045
+ metavar='<request_id>',
1046
+ help='Id of the sharing request')
1047
+ gs_decline_parser.add_argument('--raw', action='store_true',
1048
+ help='Print the raw JSON response instead of parsed output')
1049
+ add_profile_argument(gs_decline_parser)
1050
+ gs_decline_parser.set_defaults(func=group_sharing_decline)
1051
+
1052
+ # group sharing cancel
1053
+ gs_cancel_parser = group_sharing_subparsers.add_parser(
1054
+ 'cancel', help='Cancel a pending group sharing request (primary side)')
1055
+ gs_cancel_parser.add_argument('--id', type=str, required=True,
1056
+ metavar='<request_id>',
1057
+ help='Id of the sharing request')
1058
+ gs_cancel_parser.add_argument('--raw', action='store_true',
1059
+ help='Print the raw JSON response instead of parsed output')
1060
+ add_profile_argument(gs_cancel_parser)
1061
+ gs_cancel_parser.set_defaults(func=group_sharing_cancel)
1062
+
937
1063
  # Automation Management
938
1064
  automation_parser = subparsers.add_parser('automations',
939
1065
  help='Manage automation triggers')
@@ -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.13.1"
8
+ VERSION = "1.14.0"
@@ -3,8 +3,17 @@
3
3
  # SPDX-License-Identifier: Apache-2.0
4
4
 
5
5
  from rmaker_lib.profile_utils import get_session_with_profile
6
+ import datetime
6
7
  import json
7
- from rmaker_cmd.node import _print_node_details
8
+ import sys
9
+ from rmaker_cmd.node import (
10
+ _get_description,
11
+ _get_request_expiration,
12
+ _get_request_id,
13
+ _get_status,
14
+ _print_api_error,
15
+ _print_node_details,
16
+ )
8
17
 
9
18
 
10
19
  def _handle_error(e, operation):
@@ -249,4 +258,211 @@ def group_list_nodes(vars=None):
249
258
  else:
250
259
  print(f"{idx}. {name} (ID: {group_id})")
251
260
  except Exception as e:
252
- _handle_error(e, "list nodes in group")
261
+ _handle_error(e, "list nodes in group")
262
+
263
+
264
+ def _split_groups(groups_arg):
265
+ return [g.strip() for g in groups_arg.split(',') if g.strip()]
266
+
267
+
268
+ def _print_group_sharing_request_details(resp, is_primary_user=False):
269
+ requests_in_resp = resp.get('sharing_requests') or []
270
+ if not requests_in_resp:
271
+ print("No pending requests")
272
+ return
273
+ request_exists = False
274
+ for request in requests_in_resp:
275
+ if request.get('request_status', '').lower() == 'declined':
276
+ continue
277
+ request_exists = True
278
+ print("\n{:<15}: {}".format('Request Id', request['request_id']))
279
+ print("{:<15}: {}".format('Group Id(s)', ','.join(request.get('group_ids', []))))
280
+ group_names = request.get('group_names') or []
281
+ if group_names:
282
+ print("{:<15}: {}".format('Group Name(s)', ','.join(group_names)))
283
+ if is_primary_user:
284
+ if 'user_name' in request and request['user_name']:
285
+ print("{:<15}: {}".format('Shared with', request['user_name']))
286
+ else:
287
+ if 'primary_user_name' in request and request['primary_user_name']:
288
+ print("{:<15}: {}".format('Shared by', request['primary_user_name']))
289
+ if 'user_role' in request and request['user_role']:
290
+ print("{:<15}: {}".format('Role', request['user_role']))
291
+ print(_get_request_expiration(request))
292
+
293
+ if not request_exists:
294
+ print("No pending requests")
295
+
296
+
297
+ def _print_group_sharing_details(resp):
298
+ try:
299
+ groups_in_resp = resp.get('group_sharing') or []
300
+ if not groups_in_resp:
301
+ print("No shared groups")
302
+ return
303
+ for group in groups_in_resp:
304
+ print("\nGroup Id: {}".format(group.get('group_id', '')))
305
+ users = group.get('users') or {}
306
+ primary_users = ','.join(users.get('primary') or [])
307
+ secondary_users = ','.join(users.get('secondary') or [])
308
+ print("{:<7}: {:<9}: {}".format('Users', 'Primary', primary_users), end='')
309
+ if secondary_users:
310
+ print("\n{:>18}: {}".format('Secondary', secondary_users), end='')
311
+ print()
312
+ except KeyError:
313
+ print("Error in displaying details...Please check API Json...Exiting...")
314
+ sys.exit(0)
315
+
316
+
317
+ def _emit(resp, raw, render):
318
+ if raw:
319
+ print(json.dumps(resp, indent=2))
320
+ return
321
+ if isinstance(resp, dict) and resp.get('status', '').lower() != 'success' and 'error_code' in resp:
322
+ _print_api_error(resp)
323
+ return
324
+ render(resp)
325
+
326
+
327
+ def group_sharing_add(vars=None):
328
+ try:
329
+ s = get_session_with_profile(vars or {})
330
+ groups = _split_groups(vars['groups'])
331
+ if not groups:
332
+ print("At least one group id must be provided in --groups")
333
+ return
334
+ user = vars.get('user')
335
+ primary = vars.get('primary') if vars.get('primary') else None
336
+ sub_role = vars.get('sub_role')
337
+ transfer = vars.get('transfer') if vars.get('transfer') else None
338
+ new_role = vars.get('new_role')
339
+ metadata = None
340
+ metadata_arg = vars.get('metadata')
341
+ if metadata_arg:
342
+ try:
343
+ metadata = json.loads(metadata_arg)
344
+ except Exception as e:
345
+ print("Invalid JSON for --metadata:", e)
346
+ return
347
+ print("Sharing group(s) with user")
348
+ resp = s.add_user_group_sharing(
349
+ groups=groups,
350
+ user_name=user,
351
+ primary=primary,
352
+ sub_role=sub_role,
353
+ metadata=metadata,
354
+ transfer=transfer,
355
+ new_role=new_role,
356
+ )
357
+
358
+ def render(r):
359
+ if r.get('status', '').lower() == 'success':
360
+ print("{:<11}: {}".format('Status', _get_status(r)))
361
+ print("{:<11}: {}".format('Description', _get_description(r)))
362
+ if 'request_id' in r:
363
+ print("{:<11}: {}".format('Request Id', _get_request_id(r)))
364
+
365
+ _emit(resp, vars.get('raw', False), render)
366
+ except Exception as e:
367
+ _handle_error(e, "add group sharing")
368
+
369
+
370
+ def group_sharing_remove(vars=None):
371
+ try:
372
+ s = get_session_with_profile(vars or {})
373
+ groups = _split_groups(vars['groups'])
374
+ if not groups:
375
+ print("At least one group id must be provided in --groups")
376
+ return
377
+ print("Removing group sharing")
378
+ resp = s.remove_user_group_sharing(
379
+ groups=','.join(groups),
380
+ user_name=vars['user'],
381
+ )
382
+
383
+ def render(r):
384
+ if r.get('status', '').lower() == 'success':
385
+ print("{}: {}".format(_get_status(r), _get_description(r)))
386
+
387
+ _emit(resp, vars.get('raw', False), render)
388
+ except Exception as e:
389
+ _handle_error(e, "remove group sharing")
390
+
391
+
392
+ def group_sharing_list(vars=None):
393
+ try:
394
+ s = get_session_with_profile(vars or {})
395
+ resp = s.get_user_group_sharing(
396
+ group_id=vars.get('group_id'),
397
+ sub_groups=vars.get('sub_groups', False),
398
+ parent_groups=vars.get('parent_groups', False),
399
+ metadata=vars.get('metadata', False),
400
+ )
401
+
402
+ def render(r):
403
+ print("Displaying group sharing details")
404
+ _print_group_sharing_details(r)
405
+
406
+ _emit(resp, vars.get('raw', False), render)
407
+ except Exception as e:
408
+ _handle_error(e, "list group sharing")
409
+
410
+
411
+ def group_sharing_list_requests(vars=None):
412
+ try:
413
+ s = get_session_with_profile(vars or {})
414
+ is_primary = vars.get('primary_user', False)
415
+ resp = s.get_user_group_sharing_requests(
416
+ request_id=vars.get('id'),
417
+ primary_user=is_primary,
418
+ )
419
+
420
+ def render(r):
421
+ print("Displaying pending group sharing requests")
422
+ print("Current (logged-in) user is set as {} user".format(
423
+ 'Primary' if is_primary else 'Secondary'))
424
+ _print_group_sharing_request_details(r, is_primary_user=is_primary)
425
+
426
+ _emit(resp, vars.get('raw', False), render)
427
+ except Exception as e:
428
+ _handle_error(e, "list group sharing requests")
429
+
430
+
431
+ def _respond_group_sharing(vars, accept):
432
+ action = 'accept' if accept else 'decline'
433
+ try:
434
+ s = get_session_with_profile(vars or {})
435
+ resp = s.respond_user_group_sharing_request(
436
+ request_id=vars['id'], accept=accept)
437
+
438
+ def render(r):
439
+ if r.get('status', '').lower() == 'success':
440
+ print("{:<11}: {}".format('Status', _get_status(r)))
441
+ print("{:<11}: {}".format('Description', _get_description(r)))
442
+
443
+ _emit(resp, vars.get('raw', False), render)
444
+ except Exception as e:
445
+ _handle_error(e, "{} group sharing request".format(action))
446
+
447
+
448
+ def group_sharing_accept(vars=None):
449
+ _respond_group_sharing(vars, accept=True)
450
+
451
+
452
+ def group_sharing_decline(vars=None):
453
+ _respond_group_sharing(vars, accept=False)
454
+
455
+
456
+ def group_sharing_cancel(vars=None):
457
+ try:
458
+ s = get_session_with_profile(vars or {})
459
+ print("Cancelling request")
460
+ resp = s.remove_user_group_sharing_request(request_id=vars['id'])
461
+
462
+ def render(r):
463
+ if r.get('status', '').lower() == 'success':
464
+ print("{}: {}".format(_get_status(r), _get_description(r)))
465
+
466
+ _emit(resp, vars.get('raw', False), render)
467
+ except Exception as e:
468
+ _handle_error(e, "cancel group sharing request")
@@ -599,6 +599,154 @@ class Session:
599
599
  log.error(f'Failed to list nodes in group: {e}')
600
600
  raise
601
601
 
602
+ # Group Sharing API methods
603
+
604
+ def add_user_group_sharing(self, groups, user_name=None, primary=None,
605
+ sub_role=None, metadata=None, transfer=None,
606
+ new_role=None):
607
+ """Share one or more groups/matter fabrics with another user."""
608
+ path = 'user/node_group/sharing'
609
+ payload = {'groups': groups}
610
+ if user_name:
611
+ payload['user_name'] = user_name
612
+ if primary is not None:
613
+ payload['primary'] = primary
614
+ if sub_role is not None:
615
+ payload['sub_role'] = sub_role
616
+ if metadata is not None:
617
+ payload['metadata'] = metadata
618
+ if transfer is not None:
619
+ payload['transfer'] = transfer
620
+ if new_role:
621
+ payload['new_role'] = new_role
622
+ url = self.config.get_host() + path
623
+ log.debug(f"Add group sharing request url : {url}")
624
+ log.debug(f"Add group sharing request payload : {json.dumps(payload)}")
625
+ try:
626
+ response = requests.put(url=url, headers=self.request_header,
627
+ json=payload, verify=configmanager.CERT_FILE)
628
+ log.debug(f"Add group sharing response : {response.text}")
629
+ response.raise_for_status()
630
+ return response.json()
631
+ except Exception as e:
632
+ log.error(f'Failed to add group sharing: {e}')
633
+ raise
634
+
635
+ def remove_user_group_sharing(self, groups, user_name):
636
+ """Remove sharing of one or more groups/matter fabrics with a user."""
637
+ path = 'user/node_group/sharing'
638
+ params = {'groups': groups, 'user_name': user_name}
639
+ url = self.config.get_host() + path
640
+ log.debug(f"Remove group sharing request url : {url}")
641
+ log.debug(f"Remove group sharing request params : {params}")
642
+ try:
643
+ response = requests.delete(url=url, headers=self.request_header,
644
+ params=params, verify=configmanager.CERT_FILE)
645
+ log.debug(f"Remove group sharing response : {response.text}")
646
+ response.raise_for_status()
647
+ return response.json()
648
+ except Exception as e:
649
+ log.error(f'Failed to remove group sharing: {e}')
650
+ raise
651
+
652
+ def get_user_group_sharing(self, group_id=None, sub_groups=False,
653
+ parent_groups=False, metadata=False):
654
+ """Fetch group/fabric sharing details for the current user."""
655
+ path = 'user/node_group/sharing'
656
+ params = {}
657
+ if group_id:
658
+ params['group_id'] = group_id
659
+ if sub_groups:
660
+ params['sub_groups'] = 'true'
661
+ if parent_groups:
662
+ params['parent_groups'] = 'true'
663
+ if metadata:
664
+ params['metadata'] = 'true'
665
+ url = self.config.get_host() + path
666
+ log.debug(f"Get group sharing request url : {url}")
667
+ log.debug(f"Get group sharing request params : {params}")
668
+ try:
669
+ response = requests.get(url=url, headers=self.request_header,
670
+ params=params, verify=configmanager.CERT_FILE)
671
+ log.debug(f"Get group sharing response : {response.text}")
672
+ response.raise_for_status()
673
+ return response.json()
674
+ except Exception as e:
675
+ log.error(f'Failed to get group sharing: {e}')
676
+ raise
677
+
678
+ def get_user_group_sharing_requests(self, request_id=None, primary_user=None):
679
+ """Fetch pending group sharing requests (sent or received).
680
+
681
+ Pagination is handled internally: the server returns at most 10
682
+ records per call along with `next_request_id`/`next_user_name`
683
+ cursors, which are echoed back until the server stops returning
684
+ them. The consolidated list is returned to the caller.
685
+ """
686
+ path = 'user/node_group/sharing/requests'
687
+ params = {}
688
+ if request_id:
689
+ params['request_id'] = request_id
690
+ if primary_user is not None:
691
+ params['primary_user'] = 'true' if primary_user else 'false'
692
+ url = self.config.get_host() + path
693
+
694
+ all_requests = []
695
+ try:
696
+ while True:
697
+ log.debug(f"Get group sharing requests url : {url}")
698
+ log.debug(f"Get group sharing requests params : {params}")
699
+ response = requests.get(url=url, headers=self.request_header,
700
+ params=params, verify=configmanager.CERT_FILE)
701
+ log.debug(f"Get group sharing requests response : {response.text}")
702
+ response.raise_for_status()
703
+ data = response.json()
704
+ all_requests.extend(data.get('sharing_requests', []))
705
+ next_req = data.get('next_request_id')
706
+ if not next_req:
707
+ break
708
+ params['start_request_id'] = next_req
709
+ next_user = data.get('next_user_name')
710
+ if next_user:
711
+ params['start_user_name'] = next_user
712
+ return {'sharing_requests': all_requests, 'total': len(all_requests)}
713
+ except Exception as e:
714
+ log.error(f'Failed to get group sharing requests: {e}')
715
+ raise
716
+
717
+ def remove_user_group_sharing_request(self, request_id):
718
+ """Cancel a pending group sharing request (primary side)."""
719
+ path = 'user/node_group/sharing/requests'
720
+ params = {'request_id': request_id}
721
+ url = self.config.get_host() + path
722
+ log.debug(f"Cancel group sharing request url : {url}")
723
+ try:
724
+ response = requests.delete(url=url, headers=self.request_header,
725
+ params=params, verify=configmanager.CERT_FILE)
726
+ log.debug(f"Cancel group sharing request response : {response.text}")
727
+ response.raise_for_status()
728
+ return response.json()
729
+ except Exception as e:
730
+ log.error(f'Failed to cancel group sharing request: {e}')
731
+ raise
732
+
733
+ def respond_user_group_sharing_request(self, request_id, accept):
734
+ """Accept or decline a group sharing request."""
735
+ path = 'user/node_group/sharing/requests'
736
+ payload = {'accept': accept, 'request_id': request_id}
737
+ url = self.config.get_host() + path
738
+ log.debug(f"Respond group sharing request url : {url}")
739
+ log.debug(f"Respond group sharing request payload : {json.dumps(payload)}")
740
+ try:
741
+ response = requests.put(url=url, headers=self.request_header,
742
+ json=payload, verify=configmanager.CERT_FILE)
743
+ log.debug(f"Respond group sharing response : {response.text}")
744
+ response.raise_for_status()
745
+ return response.json()
746
+ except Exception as e:
747
+ log.error(f'Failed to respond to group sharing request: {e}')
748
+ raise
749
+
602
750
  # Automation Trigger API methods
603
751
 
604
752
  def create_automation(self, payload):