synapse-sdk 1.0.0a23__py3-none-any.whl → 2025.12.3__py3-none-any.whl

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 (228) hide show
  1. synapse_sdk/__init__.py +24 -0
  2. synapse_sdk/cli/__init__.py +310 -5
  3. synapse_sdk/cli/alias/__init__.py +22 -0
  4. synapse_sdk/cli/alias/create.py +36 -0
  5. synapse_sdk/cli/alias/dataclass.py +31 -0
  6. synapse_sdk/cli/alias/default.py +16 -0
  7. synapse_sdk/cli/alias/delete.py +15 -0
  8. synapse_sdk/cli/alias/list.py +19 -0
  9. synapse_sdk/cli/alias/read.py +15 -0
  10. synapse_sdk/cli/alias/update.py +17 -0
  11. synapse_sdk/cli/alias/utils.py +61 -0
  12. synapse_sdk/cli/code_server.py +687 -0
  13. synapse_sdk/cli/config.py +440 -0
  14. synapse_sdk/cli/devtools.py +90 -0
  15. synapse_sdk/cli/plugin/__init__.py +33 -0
  16. synapse_sdk/cli/{create_plugin.py → plugin/create.py} +2 -2
  17. synapse_sdk/{plugins/cli → cli/plugin}/publish.py +23 -15
  18. synapse_sdk/clients/agent/__init__.py +9 -3
  19. synapse_sdk/clients/agent/container.py +143 -0
  20. synapse_sdk/clients/agent/core.py +19 -0
  21. synapse_sdk/clients/agent/ray.py +298 -9
  22. synapse_sdk/clients/backend/__init__.py +30 -12
  23. synapse_sdk/clients/backend/annotation.py +13 -5
  24. synapse_sdk/clients/backend/core.py +31 -4
  25. synapse_sdk/clients/backend/data_collection.py +186 -0
  26. synapse_sdk/clients/backend/hitl.py +17 -0
  27. synapse_sdk/clients/backend/integration.py +16 -1
  28. synapse_sdk/clients/backend/ml.py +5 -1
  29. synapse_sdk/clients/backend/models.py +78 -0
  30. synapse_sdk/clients/base.py +384 -41
  31. synapse_sdk/clients/ray/serve.py +2 -0
  32. synapse_sdk/clients/validators/collections.py +31 -0
  33. synapse_sdk/devtools/config.py +94 -0
  34. synapse_sdk/devtools/server.py +41 -0
  35. synapse_sdk/devtools/streamlit_app/__init__.py +5 -0
  36. synapse_sdk/devtools/streamlit_app/app.py +128 -0
  37. synapse_sdk/devtools/streamlit_app/services/__init__.py +11 -0
  38. synapse_sdk/devtools/streamlit_app/services/job_service.py +233 -0
  39. synapse_sdk/devtools/streamlit_app/services/plugin_service.py +236 -0
  40. synapse_sdk/devtools/streamlit_app/services/serve_service.py +95 -0
  41. synapse_sdk/devtools/streamlit_app/ui/__init__.py +15 -0
  42. synapse_sdk/devtools/streamlit_app/ui/config_tab.py +76 -0
  43. synapse_sdk/devtools/streamlit_app/ui/deployment_tab.py +66 -0
  44. synapse_sdk/devtools/streamlit_app/ui/http_tab.py +125 -0
  45. synapse_sdk/devtools/streamlit_app/ui/jobs_tab.py +573 -0
  46. synapse_sdk/devtools/streamlit_app/ui/serve_tab.py +346 -0
  47. synapse_sdk/devtools/streamlit_app/ui/status_bar.py +118 -0
  48. synapse_sdk/devtools/streamlit_app/utils/__init__.py +40 -0
  49. synapse_sdk/devtools/streamlit_app/utils/json_viewer.py +197 -0
  50. synapse_sdk/devtools/streamlit_app/utils/log_formatter.py +38 -0
  51. synapse_sdk/devtools/streamlit_app/utils/styles.py +241 -0
  52. synapse_sdk/devtools/streamlit_app/utils/ui_components.py +289 -0
  53. synapse_sdk/devtools/streamlit_app.py +10 -0
  54. synapse_sdk/loggers.py +120 -9
  55. synapse_sdk/plugins/README.md +1340 -0
  56. synapse_sdk/plugins/__init__.py +0 -13
  57. synapse_sdk/plugins/categories/base.py +117 -11
  58. synapse_sdk/plugins/categories/data_validation/actions/validation.py +72 -0
  59. synapse_sdk/plugins/categories/data_validation/templates/plugin/validation.py +33 -5
  60. synapse_sdk/plugins/categories/export/actions/__init__.py +3 -0
  61. synapse_sdk/plugins/categories/export/actions/export/__init__.py +28 -0
  62. synapse_sdk/plugins/categories/export/actions/export/action.py +165 -0
  63. synapse_sdk/plugins/categories/export/actions/export/enums.py +113 -0
  64. synapse_sdk/plugins/categories/export/actions/export/exceptions.py +53 -0
  65. synapse_sdk/plugins/categories/export/actions/export/models.py +74 -0
  66. synapse_sdk/plugins/categories/export/actions/export/run.py +195 -0
  67. synapse_sdk/plugins/categories/export/actions/export/utils.py +187 -0
  68. synapse_sdk/plugins/categories/export/templates/config.yaml +21 -0
  69. synapse_sdk/plugins/categories/export/templates/plugin/__init__.py +390 -0
  70. synapse_sdk/plugins/categories/export/templates/plugin/export.py +160 -0
  71. synapse_sdk/plugins/categories/neural_net/actions/deployment.py +13 -12
  72. synapse_sdk/plugins/categories/neural_net/actions/train.py +1134 -31
  73. synapse_sdk/plugins/categories/neural_net/actions/tune.py +534 -0
  74. synapse_sdk/plugins/categories/neural_net/base/inference.py +1 -1
  75. synapse_sdk/plugins/categories/neural_net/templates/config.yaml +32 -4
  76. synapse_sdk/plugins/categories/neural_net/templates/plugin/inference.py +26 -10
  77. synapse_sdk/plugins/categories/pre_annotation/actions/__init__.py +4 -0
  78. synapse_sdk/plugins/categories/pre_annotation/actions/pre_annotation/__init__.py +3 -0
  79. synapse_sdk/plugins/categories/{export/actions/export.py → pre_annotation/actions/pre_annotation/action.py} +4 -4
  80. synapse_sdk/plugins/categories/pre_annotation/actions/to_task/__init__.py +28 -0
  81. synapse_sdk/plugins/categories/pre_annotation/actions/to_task/action.py +148 -0
  82. synapse_sdk/plugins/categories/pre_annotation/actions/to_task/enums.py +269 -0
  83. synapse_sdk/plugins/categories/pre_annotation/actions/to_task/exceptions.py +14 -0
  84. synapse_sdk/plugins/categories/pre_annotation/actions/to_task/factory.py +76 -0
  85. synapse_sdk/plugins/categories/pre_annotation/actions/to_task/models.py +100 -0
  86. synapse_sdk/plugins/categories/pre_annotation/actions/to_task/orchestrator.py +248 -0
  87. synapse_sdk/plugins/categories/pre_annotation/actions/to_task/run.py +64 -0
  88. synapse_sdk/plugins/categories/pre_annotation/actions/to_task/strategies/__init__.py +17 -0
  89. synapse_sdk/plugins/categories/pre_annotation/actions/to_task/strategies/annotation.py +265 -0
  90. synapse_sdk/plugins/categories/pre_annotation/actions/to_task/strategies/base.py +170 -0
  91. synapse_sdk/plugins/categories/pre_annotation/actions/to_task/strategies/extraction.py +83 -0
  92. synapse_sdk/plugins/categories/pre_annotation/actions/to_task/strategies/metrics.py +92 -0
  93. synapse_sdk/plugins/categories/pre_annotation/actions/to_task/strategies/preprocessor.py +243 -0
  94. synapse_sdk/plugins/categories/pre_annotation/actions/to_task/strategies/validation.py +143 -0
  95. synapse_sdk/plugins/categories/pre_annotation/templates/config.yaml +19 -0
  96. synapse_sdk/plugins/categories/pre_annotation/templates/plugin/to_task.py +40 -0
  97. synapse_sdk/plugins/categories/smart_tool/templates/config.yaml +2 -0
  98. synapse_sdk/plugins/categories/upload/__init__.py +0 -0
  99. synapse_sdk/plugins/categories/upload/actions/__init__.py +0 -0
  100. synapse_sdk/plugins/categories/upload/actions/upload/__init__.py +19 -0
  101. synapse_sdk/plugins/categories/upload/actions/upload/action.py +236 -0
  102. synapse_sdk/plugins/categories/upload/actions/upload/context.py +185 -0
  103. synapse_sdk/plugins/categories/upload/actions/upload/enums.py +493 -0
  104. synapse_sdk/plugins/categories/upload/actions/upload/exceptions.py +36 -0
  105. synapse_sdk/plugins/categories/upload/actions/upload/factory.py +138 -0
  106. synapse_sdk/plugins/categories/upload/actions/upload/models.py +214 -0
  107. synapse_sdk/plugins/categories/upload/actions/upload/orchestrator.py +183 -0
  108. synapse_sdk/plugins/categories/upload/actions/upload/registry.py +113 -0
  109. synapse_sdk/plugins/categories/upload/actions/upload/run.py +179 -0
  110. synapse_sdk/plugins/categories/upload/actions/upload/steps/__init__.py +1 -0
  111. synapse_sdk/plugins/categories/upload/actions/upload/steps/base.py +107 -0
  112. synapse_sdk/plugins/categories/upload/actions/upload/steps/cleanup.py +62 -0
  113. synapse_sdk/plugins/categories/upload/actions/upload/steps/collection.py +63 -0
  114. synapse_sdk/plugins/categories/upload/actions/upload/steps/generate.py +91 -0
  115. synapse_sdk/plugins/categories/upload/actions/upload/steps/initialize.py +82 -0
  116. synapse_sdk/plugins/categories/upload/actions/upload/steps/metadata.py +235 -0
  117. synapse_sdk/plugins/categories/upload/actions/upload/steps/organize.py +201 -0
  118. synapse_sdk/plugins/categories/upload/actions/upload/steps/upload.py +104 -0
  119. synapse_sdk/plugins/categories/upload/actions/upload/steps/validate.py +71 -0
  120. synapse_sdk/plugins/categories/upload/actions/upload/strategies/__init__.py +1 -0
  121. synapse_sdk/plugins/categories/upload/actions/upload/strategies/base.py +82 -0
  122. synapse_sdk/plugins/categories/upload/actions/upload/strategies/data_unit/__init__.py +1 -0
  123. synapse_sdk/plugins/categories/upload/actions/upload/strategies/data_unit/batch.py +39 -0
  124. synapse_sdk/plugins/categories/upload/actions/upload/strategies/data_unit/single.py +29 -0
  125. synapse_sdk/plugins/categories/upload/actions/upload/strategies/file_discovery/__init__.py +1 -0
  126. synapse_sdk/plugins/categories/upload/actions/upload/strategies/file_discovery/flat.py +300 -0
  127. synapse_sdk/plugins/categories/upload/actions/upload/strategies/file_discovery/recursive.py +287 -0
  128. synapse_sdk/plugins/categories/upload/actions/upload/strategies/metadata/__init__.py +1 -0
  129. synapse_sdk/plugins/categories/upload/actions/upload/strategies/metadata/excel.py +174 -0
  130. synapse_sdk/plugins/categories/upload/actions/upload/strategies/metadata/none.py +16 -0
  131. synapse_sdk/plugins/categories/upload/actions/upload/strategies/upload/__init__.py +1 -0
  132. synapse_sdk/plugins/categories/upload/actions/upload/strategies/upload/sync.py +84 -0
  133. synapse_sdk/plugins/categories/upload/actions/upload/strategies/validation/__init__.py +1 -0
  134. synapse_sdk/plugins/categories/upload/actions/upload/strategies/validation/default.py +60 -0
  135. synapse_sdk/plugins/categories/upload/actions/upload/utils.py +250 -0
  136. synapse_sdk/plugins/categories/upload/templates/README.md +470 -0
  137. synapse_sdk/plugins/categories/upload/templates/config.yaml +33 -0
  138. synapse_sdk/plugins/categories/upload/templates/plugin/__init__.py +310 -0
  139. synapse_sdk/plugins/categories/upload/templates/plugin/upload.py +102 -0
  140. synapse_sdk/plugins/enums.py +3 -1
  141. synapse_sdk/plugins/models.py +148 -11
  142. synapse_sdk/plugins/templates/plugin-config-schema.json +406 -0
  143. synapse_sdk/plugins/templates/schema.json +491 -0
  144. synapse_sdk/plugins/templates/synapse-{{cookiecutter.plugin_code}}-plugin/config.yaml +1 -0
  145. synapse_sdk/plugins/templates/synapse-{{cookiecutter.plugin_code}}-plugin/requirements.txt +1 -1
  146. synapse_sdk/plugins/utils/__init__.py +46 -0
  147. synapse_sdk/plugins/utils/actions.py +119 -0
  148. synapse_sdk/plugins/utils/config.py +203 -0
  149. synapse_sdk/plugins/{utils.py → utils/legacy.py} +26 -46
  150. synapse_sdk/plugins/utils/ray_gcs.py +66 -0
  151. synapse_sdk/plugins/utils/registry.py +58 -0
  152. synapse_sdk/shared/__init__.py +25 -0
  153. synapse_sdk/shared/enums.py +93 -0
  154. synapse_sdk/types.py +19 -0
  155. synapse_sdk/utils/converters/__init__.py +240 -0
  156. synapse_sdk/utils/converters/coco/__init__.py +0 -0
  157. synapse_sdk/utils/converters/coco/from_dm.py +322 -0
  158. synapse_sdk/utils/converters/coco/to_dm.py +215 -0
  159. synapse_sdk/utils/converters/dm/__init__.py +57 -0
  160. synapse_sdk/utils/converters/dm/base.py +137 -0
  161. synapse_sdk/utils/converters/dm/from_v1.py +273 -0
  162. synapse_sdk/utils/converters/dm/to_v1.py +321 -0
  163. synapse_sdk/utils/converters/dm/tools/__init__.py +214 -0
  164. synapse_sdk/utils/converters/dm/tools/answer.py +95 -0
  165. synapse_sdk/utils/converters/dm/tools/bounding_box.py +132 -0
  166. synapse_sdk/utils/converters/dm/tools/bounding_box_3d.py +121 -0
  167. synapse_sdk/utils/converters/dm/tools/classification.py +75 -0
  168. synapse_sdk/utils/converters/dm/tools/keypoint.py +117 -0
  169. synapse_sdk/utils/converters/dm/tools/named_entity.py +111 -0
  170. synapse_sdk/utils/converters/dm/tools/polygon.py +122 -0
  171. synapse_sdk/utils/converters/dm/tools/polyline.py +124 -0
  172. synapse_sdk/utils/converters/dm/tools/prompt.py +94 -0
  173. synapse_sdk/utils/converters/dm/tools/relation.py +86 -0
  174. synapse_sdk/utils/converters/dm/tools/segmentation.py +141 -0
  175. synapse_sdk/utils/converters/dm/tools/segmentation_3d.py +83 -0
  176. synapse_sdk/utils/converters/dm/types.py +168 -0
  177. synapse_sdk/utils/converters/dm/utils.py +162 -0
  178. synapse_sdk/utils/converters/dm_legacy/__init__.py +56 -0
  179. synapse_sdk/utils/converters/dm_legacy/from_v1.py +627 -0
  180. synapse_sdk/utils/converters/dm_legacy/to_v1.py +367 -0
  181. synapse_sdk/utils/converters/pascal/__init__.py +0 -0
  182. synapse_sdk/utils/converters/pascal/from_dm.py +244 -0
  183. synapse_sdk/utils/converters/pascal/to_dm.py +214 -0
  184. synapse_sdk/utils/converters/yolo/__init__.py +0 -0
  185. synapse_sdk/utils/converters/yolo/from_dm.py +384 -0
  186. synapse_sdk/utils/converters/yolo/to_dm.py +267 -0
  187. synapse_sdk/utils/dataset.py +46 -0
  188. synapse_sdk/utils/encryption.py +158 -0
  189. synapse_sdk/utils/file/__init__.py +58 -0
  190. synapse_sdk/utils/file/archive.py +32 -0
  191. synapse_sdk/utils/file/checksum.py +56 -0
  192. synapse_sdk/utils/file/chunking.py +31 -0
  193. synapse_sdk/utils/file/download.py +385 -0
  194. synapse_sdk/utils/file/encoding.py +40 -0
  195. synapse_sdk/utils/file/io.py +22 -0
  196. synapse_sdk/utils/file/upload.py +165 -0
  197. synapse_sdk/utils/file/video/__init__.py +29 -0
  198. synapse_sdk/utils/file/video/transcode.py +307 -0
  199. synapse_sdk/utils/file.py.backup +301 -0
  200. synapse_sdk/utils/http.py +138 -0
  201. synapse_sdk/utils/network.py +309 -0
  202. synapse_sdk/utils/storage/__init__.py +72 -0
  203. synapse_sdk/utils/storage/providers/__init__.py +183 -0
  204. synapse_sdk/utils/storage/providers/file_system.py +134 -0
  205. synapse_sdk/utils/storage/providers/gcp.py +13 -0
  206. synapse_sdk/utils/storage/providers/http.py +190 -0
  207. synapse_sdk/utils/storage/providers/s3.py +91 -0
  208. synapse_sdk/utils/storage/providers/sftp.py +47 -0
  209. synapse_sdk/utils/storage/registry.py +17 -0
  210. synapse_sdk-2025.12.3.dist-info/METADATA +123 -0
  211. synapse_sdk-2025.12.3.dist-info/RECORD +279 -0
  212. {synapse_sdk-1.0.0a23.dist-info → synapse_sdk-2025.12.3.dist-info}/WHEEL +1 -1
  213. synapse_sdk/clients/backend/dataset.py +0 -51
  214. synapse_sdk/plugins/categories/import/actions/import.py +0 -10
  215. synapse_sdk/plugins/cli/__init__.py +0 -21
  216. synapse_sdk/plugins/templates/synapse-{{cookiecutter.plugin_code}}-plugin/.env +0 -24
  217. synapse_sdk/plugins/templates/synapse-{{cookiecutter.plugin_code}}-plugin/.env.dist +0 -24
  218. synapse_sdk/plugins/templates/synapse-{{cookiecutter.plugin_code}}-plugin/main.py +0 -4
  219. synapse_sdk/utils/file.py +0 -168
  220. synapse_sdk/utils/storage.py +0 -91
  221. synapse_sdk-1.0.0a23.dist-info/METADATA +0 -44
  222. synapse_sdk-1.0.0a23.dist-info/RECORD +0 -114
  223. /synapse_sdk/{plugins/cli → cli/plugin}/run.py +0 -0
  224. /synapse_sdk/{plugins/categories/import → clients/validators}/__init__.py +0 -0
  225. /synapse_sdk/{plugins/categories/import/actions → devtools}/__init__.py +0 -0
  226. {synapse_sdk-1.0.0a23.dist-info → synapse_sdk-2025.12.3.dist-info}/entry_points.txt +0 -0
  227. {synapse_sdk-1.0.0a23.dist-info → synapse_sdk-2025.12.3.dist-info/licenses}/LICENSE +0 -0
  228. {synapse_sdk-1.0.0a23.dist-info → synapse_sdk-2025.12.3.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,440 @@
1
+ import os
2
+
3
+ import click
4
+ import inquirer
5
+ import requests
6
+
7
+ from synapse_sdk.devtools.config import (
8
+ clear_backend_config,
9
+ get_backend_config,
10
+ load_devtools_config,
11
+ save_devtools_config,
12
+ set_backend_config,
13
+ )
14
+
15
+
16
+ def clear_screen():
17
+ """Clear the terminal screen"""
18
+ os.system('cls' if os.name == 'nt' else 'clear')
19
+
20
+
21
+ def check_backend_connection(host, token):
22
+ """Test backend connection with given credentials"""
23
+ try:
24
+ # Try an authenticated endpoint to verify token validity
25
+ response = requests.get(
26
+ f'{host}/users/me/',
27
+ headers={'Synapse-Access-Token': f'Token {token}'},
28
+ timeout=5,
29
+ )
30
+
31
+ if response.status_code == 200:
32
+ return True, 'Connection successful'
33
+ elif response.status_code == 401:
34
+ return False, 'Invalid token (401)'
35
+ elif response.status_code == 403:
36
+ return False, 'Access forbidden (403)'
37
+ elif response.status_code == 404:
38
+ # If /users/me/ doesn't exist, try /health as fallback
39
+ try:
40
+ health_response = requests.get(
41
+ f'{host}/health',
42
+ headers={'Synapse-Access-Token': f'Token {token}'},
43
+ timeout=3,
44
+ )
45
+ if health_response.status_code == 200:
46
+ return True, 'Connection successful'
47
+ elif health_response.status_code == 401:
48
+ return False, 'Invalid token (401)'
49
+ elif health_response.status_code == 403:
50
+ return False, 'Access forbidden (403)'
51
+ else:
52
+ return False, f'HTTP {health_response.status_code}'
53
+ except Exception:
54
+ return False, 'Endpoint not found (404)'
55
+ else:
56
+ return False, f'HTTP {response.status_code}'
57
+
58
+ except requests.exceptions.Timeout:
59
+ return False, 'Connection timeout (>5s)'
60
+ except requests.exceptions.ConnectionError:
61
+ return False, 'Connection failed'
62
+ except Exception as e:
63
+ return False, f'Connection error: {str(e)}'
64
+
65
+
66
+ def check_agent_connection(agent_url, agent_token):
67
+ """Test agent connection with given credentials"""
68
+ if not agent_url or not agent_token:
69
+ return True, 'Agent configured (no URL/token to test)'
70
+
71
+ try:
72
+ # Try to connect to the agent
73
+ response = requests.get(
74
+ f'{agent_url}/health/',
75
+ headers={'Authorization': agent_token},
76
+ timeout=5,
77
+ )
78
+
79
+ if response.status_code == 200:
80
+ return True, 'Agent connection successful'
81
+ elif response.status_code == 401:
82
+ return False, 'Invalid agent token (401)'
83
+ elif response.status_code == 403:
84
+ return False, 'Agent access forbidden (403)'
85
+ else:
86
+ return False, f'Agent HTTP {response.status_code}'
87
+
88
+ except requests.exceptions.Timeout:
89
+ return False, 'Agent connection timeout (>5s)'
90
+ except requests.exceptions.ConnectionError:
91
+ return False, 'Agent connection failed'
92
+ except Exception as e:
93
+ return False, f'Agent error: {str(e)}'
94
+
95
+
96
+ def get_agent_config():
97
+ """Get current agent configuration"""
98
+ config = load_devtools_config()
99
+ return config.get('agent', {})
100
+
101
+
102
+ def get_tenant_config():
103
+ """Get current tenant configuration"""
104
+ config = load_devtools_config()
105
+ return config.get('tenant', {})
106
+
107
+
108
+ def set_tenant_config(tenant_code: str, tenant_name: str = None):
109
+ """Set tenant configuration"""
110
+ config = load_devtools_config()
111
+ config['tenant'] = {'code': tenant_code}
112
+ if tenant_name:
113
+ config['tenant']['name'] = tenant_name
114
+ save_devtools_config(config)
115
+
116
+
117
+ def clear_tenant_config():
118
+ """Clear tenant configuration"""
119
+ config = load_devtools_config()
120
+ if 'tenant' in config:
121
+ del config['tenant']
122
+ save_devtools_config(config)
123
+
124
+
125
+ def set_agent_config(agent_id: str, agent_name: str = None, agent_url: str = None, agent_token: str = None):
126
+ """Set agent configuration"""
127
+ config = load_devtools_config()
128
+ config['agent'] = {'id': agent_id}
129
+ if agent_name:
130
+ config['agent']['name'] = agent_name
131
+ if agent_url:
132
+ config['agent']['url'] = agent_url
133
+ if agent_token:
134
+ config['agent']['token'] = agent_token
135
+
136
+ save_devtools_config(config)
137
+
138
+
139
+ def clear_agent_config():
140
+ """Clear agent configuration"""
141
+ config = load_devtools_config()
142
+ if 'agent' in config:
143
+ del config['agent']
144
+ save_devtools_config(config)
145
+
146
+
147
+ def fetch_agents_from_backend():
148
+ """Fetch available agents from the backend"""
149
+ backend_config = get_backend_config()
150
+ if not backend_config:
151
+ return None, 'No backend configuration found. Configure backend first.'
152
+
153
+ def extract_uuid(string):
154
+ import re
155
+
156
+ """Extract UUID between 'agents/' and '/node_install_script' from a string."""
157
+ pattern = r'agents/([a-f0-9]{40})/node_install_script'
158
+ match = re.search(pattern, string)
159
+ return match.group(1) if match else None
160
+
161
+ try:
162
+ response = requests.get(
163
+ f'{backend_config["host"]}/agents/',
164
+ headers={'Synapse-Access-Token': f'Token {backend_config["token"]}'},
165
+ timeout=10,
166
+ )
167
+ if response.status_code == 200:
168
+ try:
169
+ data = response.json()
170
+ results = data.get('results', [])
171
+ for result in results:
172
+ _node_install_script = result.get('node_install_script')
173
+ if _node_install_script:
174
+ result['token'] = extract_uuid(_node_install_script)
175
+ return results, None
176
+ except ValueError:
177
+ return None, 'Invalid JSON response from server'
178
+ elif response.status_code == 401:
179
+ return None, 'Authentication failed. Check your backend token.'
180
+ elif response.status_code == 403:
181
+ return None, 'Access forbidden. Check your permissions.'
182
+ else:
183
+ return None, f'Failed to fetch agents: HTTP {response.status_code}'
184
+
185
+ except requests.exceptions.Timeout:
186
+ return None, 'Request timeout. Check your network connection.'
187
+ except requests.exceptions.ConnectionError:
188
+ return None, 'Connection failed. Check backend host URL.'
189
+ except Exception as e:
190
+ return None, f'Error fetching agents: {str(e)}'
191
+
192
+
193
+ def configure_backend():
194
+ """Interactive backend configuration"""
195
+ backend_config = get_backend_config()
196
+
197
+ if backend_config:
198
+ click.echo(f'Current backend: {backend_config["host"]}')
199
+ click.echo(f'Token: {backend_config["token"]}')
200
+ click.echo()
201
+
202
+ questions = [
203
+ inquirer.List(
204
+ 'action',
205
+ message='What would you like to do?',
206
+ choices=[
207
+ ('Configure new backend', 'configure'),
208
+ ('Show current configuration', 'show'),
209
+ ('Clear configuration', 'clear'),
210
+ ('← Back to main menu', 'back'),
211
+ ],
212
+ )
213
+ ]
214
+
215
+ answers = inquirer.prompt(questions)
216
+ if not answers or answers['action'] == 'back':
217
+ return
218
+
219
+ if answers['action'] == 'show':
220
+ if backend_config:
221
+ click.echo(click.style('\n✓ Backend Configuration:', fg='green'))
222
+ click.echo(f' Host: {backend_config["host"]}')
223
+ click.echo(f' Token: {backend_config["token"]}')
224
+ else:
225
+ click.echo(click.style('\n⚠ No backend configuration found.', fg='yellow'))
226
+ return
227
+
228
+ if answers['action'] == 'clear':
229
+ confirm = inquirer.confirm('Are you sure you want to clear the backend configuration?')
230
+ if confirm:
231
+ clear_backend_config()
232
+ click.echo(click.style('\n✓ Backend configuration cleared.', fg='yellow'))
233
+ return
234
+
235
+ if answers['action'] == 'configure':
236
+ config_questions = [
237
+ inquirer.Text(
238
+ 'host',
239
+ message='Backend host URL',
240
+ default=backend_config['host'] if backend_config else 'https://api.synapse.sh',
241
+ ),
242
+ inquirer.Text('token', default=backend_config['token'] if backend_config else '', message='API token'),
243
+ ]
244
+
245
+ config_answers = inquirer.prompt(config_questions)
246
+ if config_answers and config_answers['token']:
247
+ set_backend_config(config_answers['host'], config_answers['token'])
248
+ click.echo(click.style('\n✓ Backend configuration saved!', fg='green'))
249
+ click.echo(f'Host: {config_answers["host"]}')
250
+ click.echo(f'Token: {config_answers["token"]}')
251
+
252
+ # Test the connection
253
+ click.echo('\nTesting connection...')
254
+ success, message = check_backend_connection(config_answers['host'], config_answers['token'])
255
+ if success:
256
+ click.echo(click.style(f'🟢 {message}', fg='green'))
257
+ else:
258
+ click.echo(click.style(f'🔴 {message}', fg='red'))
259
+
260
+
261
+ def configure_agent():
262
+ """Interactive agent configuration"""
263
+ agent_config = get_agent_config()
264
+
265
+ if agent_config:
266
+ click.echo(f'Current agent: {agent_config.get("name", "Unknown")} ({agent_config.get("id")})')
267
+ click.echo()
268
+
269
+ questions = [
270
+ inquirer.List(
271
+ 'action',
272
+ message='What would you like to do?',
273
+ choices=[
274
+ ('Select agent', 'select'),
275
+ ('Set agent ID manually', 'manual'),
276
+ ('← Back to main menu', 'back'),
277
+ ],
278
+ )
279
+ ]
280
+
281
+ answers = inquirer.prompt(questions)
282
+ if not answers or answers['action'] == 'back':
283
+ return
284
+
285
+ if answers['action'] == 'select':
286
+ click.echo('Fetching available agents...')
287
+ agents, error = fetch_agents_from_backend()
288
+
289
+ if error:
290
+ click.echo(click.style(f'\nError: {error}', fg='red'))
291
+ return
292
+
293
+ if not agents:
294
+ click.echo(click.style('\n⚠ No agents found in current workspace.', fg='yellow'))
295
+ return
296
+
297
+ # Create choices for agent selection
298
+ agent_choices = []
299
+ for agent in agents:
300
+ status_indicator = '🟢' if agent.get('status', '').lower() == 'connected' else '🔴'
301
+ display_name = f'{status_indicator} {agent["name"]} ({agent["id"]})'
302
+ if agent.get('url'):
303
+ display_name += f' - {agent["url"]}'
304
+ agent_choices.append((display_name, agent))
305
+
306
+ agent_choices.append(('← Cancel', None))
307
+
308
+ agent_questions = [inquirer.List('selected_agent', message='Select an agent:', choices=agent_choices)]
309
+
310
+ agent_answers = inquirer.prompt(agent_questions)
311
+ if agent_answers and agent_answers['selected_agent']:
312
+ selected = agent_answers['selected_agent']
313
+ set_agent_config(selected['id'], selected.get('name'), selected.get('url'), selected.get('token'))
314
+ click.echo(click.style('\n✓ Agent configured!', fg='green'))
315
+ click.echo(f'Selected: {selected["name"]} ({selected["id"]})')
316
+ if selected.get('url'):
317
+ click.echo(f'URL: {selected["url"]}')
318
+
319
+ # Test the agent connection if URL and token are provided
320
+ if selected.get('url') and selected.get('token'):
321
+ click.echo('\nTesting agent connection...')
322
+ success, message = check_agent_connection(selected['url'], selected['token'])
323
+ if success:
324
+ click.echo(click.style(f'🟢 {message}', fg='green'))
325
+ else:
326
+ click.echo(click.style(f'🔴 {message}', fg='red'))
327
+
328
+ if answers['action'] == 'manual':
329
+ manual_questions = [
330
+ inquirer.Text('agent_id', message='Agent ID', default=agent_config.get('id', '') if agent_config else ''),
331
+ inquirer.Text(
332
+ 'agent_url', message='Agent URL', default=agent_config.get('url', '') if agent_config else ''
333
+ ),
334
+ inquirer.Text(
335
+ 'agent_token', message='Agent Token', default=agent_config.get('token', '') if agent_config else ''
336
+ ),
337
+ ]
338
+
339
+ manual_answers = inquirer.prompt(manual_questions)
340
+ if manual_answers and manual_answers['agent_id']:
341
+ set_agent_config(
342
+ manual_answers['agent_id'], None, manual_answers.get('agent_url'), manual_answers.get('agent_token')
343
+ )
344
+ click.echo(click.style('\n✓ Agent configured!', fg='green'))
345
+ click.echo(f'Agent ID: {manual_answers["agent_id"]}')
346
+ if manual_answers.get('agent_url'):
347
+ click.echo(f'Agent URL: {manual_answers["agent_url"]}')
348
+
349
+ # Test the agent connection if URL and token are provided
350
+ if manual_answers.get('agent_url') and manual_answers.get('agent_token'):
351
+ click.echo('\nTesting agent connection...')
352
+ success, message = check_agent_connection(manual_answers['agent_url'], manual_answers['agent_token'])
353
+ if success:
354
+ click.echo(click.style(f'🟢 {message}', fg='green'))
355
+ else:
356
+ click.echo(click.style(f'🔴 {message}', fg='red'))
357
+ return
358
+
359
+
360
+ def show_current_config():
361
+ """Show all current configuration"""
362
+ backend_config = get_backend_config()
363
+ agent_config = get_agent_config()
364
+
365
+ click.echo(click.style('\n📋 Current Configuration', fg='cyan', bold=True))
366
+ click.echo('=' * 30)
367
+
368
+ # Backend section
369
+ click.echo(click.style('\n🔗 Backend:', fg='blue', bold=True))
370
+ if backend_config:
371
+ click.echo(f' Host: {backend_config["host"]}')
372
+ click.echo(f' Token: {backend_config["token"]}')
373
+ click.echo(click.style(' Status: ✓ Configured', fg='green'))
374
+ else:
375
+ click.echo(click.style(' Status: ✗ Not configured', fg='red'))
376
+
377
+ # Agent section
378
+ click.echo(click.style('\n🤖 Agent:', fg='blue', bold=True))
379
+ if agent_config:
380
+ click.echo(f' ID: {agent_config.get("id", "Not set")}')
381
+ if 'name' in agent_config:
382
+ click.echo(f' Name: {agent_config["name"]}')
383
+ click.echo(click.style(' Status: ✓ Configured', fg='green'))
384
+ else:
385
+ click.echo(click.style(' Status: ✗ Not configured', fg='red'))
386
+
387
+
388
+ def interactive_config():
389
+ while True:
390
+ clear_screen()
391
+ click.echo(click.style('🔧 Configuration', fg='cyan', bold=True))
392
+ click.echo('Configure your Synapse settings\n')
393
+
394
+ questions = [
395
+ inquirer.List(
396
+ 'choice',
397
+ message='What would you like to configure?',
398
+ choices=[
399
+ ('Synapse Backend Host', 'backend'),
400
+ ('Synapse Agent', 'agent'),
401
+ ('Show Current Config', 'show'),
402
+ ('← Back to Main Menu', 'exit'),
403
+ ],
404
+ )
405
+ ]
406
+
407
+ try:
408
+ answers = inquirer.prompt(questions)
409
+ if not answers or answers['choice'] == 'exit':
410
+ clear_screen() # Clear screen when exiting config
411
+ break
412
+
413
+ clear_screen()
414
+
415
+ if answers['choice'] == 'backend':
416
+ configure_backend()
417
+ click.echo('\nPress Enter to continue...')
418
+ input()
419
+ elif answers['choice'] == 'agent':
420
+ configure_agent()
421
+ click.echo('\nPress Enter to continue...')
422
+ input()
423
+ elif answers['choice'] == 'show':
424
+ show_current_config()
425
+ click.echo('\nPress Enter to continue...')
426
+ input()
427
+
428
+ except (KeyboardInterrupt, EOFError):
429
+ clear_screen() # Clear screen on interrupt
430
+ break
431
+ except Exception as e:
432
+ click.echo(click.style(f'\nError: {str(e)}', fg='red'))
433
+ click.echo('\nPress Enter to continue...')
434
+ input()
435
+
436
+
437
+ @click.command()
438
+ def config():
439
+ """Configure Synapse settings interactively"""
440
+ interactive_config()
@@ -0,0 +1,90 @@
1
+ import os
2
+ import subprocess
3
+ import sys
4
+ import time
5
+
6
+ import click
7
+
8
+ from synapse_sdk.i18n import gettext as _
9
+
10
+
11
+ @click.command()
12
+ @click.option('--host', default=None, help='Host to bind the devtools server')
13
+ @click.option('--port', default=None, type=int, help='Port to bind the devtools server')
14
+ @click.option('--debug', is_flag=True, help='Run in debug mode')
15
+ def devtools(host, port, debug):
16
+ """Start the Synapse devtools web interface"""
17
+
18
+ try:
19
+ import importlib.util
20
+
21
+ if not importlib.util.find_spec('streamlit') or not importlib.util.find_spec('streamlit_ace'):
22
+ raise ImportError('Missing dependencies')
23
+ except ImportError:
24
+ click.echo(
25
+ click.style(
26
+ _('Devtools dependencies not installed. Install with: pip install synapse-sdk[devtools]'), fg='red'
27
+ ),
28
+ err=True,
29
+ )
30
+ click.echo(
31
+ click.style(_('Specifically, you need: pip install streamlit streamlit-ace'), fg='yellow'),
32
+ err=True,
33
+ )
34
+ sys.exit(1)
35
+
36
+ click.echo('Starting Synapse DevTools...')
37
+
38
+ # Get the path to the streamlit app
39
+ from pathlib import Path
40
+
41
+ app_path = Path(__file__).parent.parent / 'devtools' / 'streamlit_app.py'
42
+
43
+ if not app_path.exists():
44
+ click.echo(click.style(f'Streamlit app not found at {app_path}', fg='red'), err=True)
45
+ sys.exit(1)
46
+
47
+ # Build streamlit command
48
+ cmd = ['streamlit', 'run', str(app_path)]
49
+
50
+ # Add host and port if specified
51
+ if host:
52
+ cmd.extend(['--server.address', host])
53
+ else:
54
+ cmd.extend(['--server.address', '0.0.0.0'])
55
+
56
+ if port:
57
+ cmd.extend(['--server.port', str(port)])
58
+ else:
59
+ cmd.extend(['--server.port', '8080']) # Default port
60
+
61
+ cmd.extend(['--server.headless', 'false'])
62
+
63
+ if debug:
64
+ cmd.extend(['--logger.level', 'debug'])
65
+ else:
66
+ cmd.extend(['--logger.level', 'error'])
67
+
68
+ # Set working directory to current directory (plugin directory)
69
+ plugin_directory = os.getcwd()
70
+
71
+ try:
72
+ # Add a small delay to ensure clean output
73
+ time.sleep(0.5)
74
+
75
+ # Start streamlit
76
+ process = subprocess.Popen(cmd, cwd=plugin_directory, env=os.environ.copy())
77
+
78
+ click.echo('Press Ctrl+C to stop')
79
+
80
+ # Wait for process
81
+ process.wait()
82
+
83
+ except KeyboardInterrupt:
84
+ click.echo(_('\nDevTools stopped.'))
85
+ if process:
86
+ process.terminate()
87
+ process.wait()
88
+ except Exception as e:
89
+ click.echo(click.style(f'Failed to start devtools: {e}', fg='red'), err=True)
90
+ sys.exit(1)
@@ -0,0 +1,33 @@
1
+ import os
2
+ import sys
3
+
4
+ import click
5
+ from dotenv import load_dotenv
6
+
7
+ from synapse_sdk.cli.alias.utils import load_dotenv_default_alias
8
+ from synapse_sdk.i18n import gettext as _
9
+
10
+ from .create import create
11
+ from .publish import publish
12
+ from .run import run
13
+
14
+ load_dotenv_default_alias()
15
+ load_dotenv(os.path.join(os.getcwd(), '.env'), override=True)
16
+ sys.path.append(os.getcwd())
17
+
18
+
19
+ @click.group(context_settings={'obj': {}, 'auto_envvar_prefix': 'SYNAPSE_PLUGIN'})
20
+ @click.option('--debug/--no-debug', default=False)
21
+ @click.pass_context
22
+ def plugin(ctx, debug):
23
+ """Manage Synapse plugins."""
24
+ ctx.ensure_object(dict)
25
+ ctx.obj['DEBUG'] = debug
26
+
27
+ if debug:
28
+ click.echo(_('Debug mode is "on"'))
29
+
30
+
31
+ plugin.add_command(create)
32
+ plugin.add_command(publish)
33
+ plugin.add_command(run)
@@ -5,6 +5,6 @@ from cookiecutter.main import cookiecutter
5
5
 
6
6
 
7
7
  @click.command()
8
- def create_plugin():
9
- project_root = Path(__file__).parent.parent
8
+ def create():
9
+ project_root = Path(__file__).parents[2]
10
10
  cookiecutter(str(project_root / 'plugins/templates'))
@@ -8,15 +8,14 @@ from synapse_sdk.plugins.models import PluginRelease
8
8
  from synapse_sdk.plugins.upload import archive
9
9
 
10
10
 
11
- @click.command()
12
- @click.option('--host', required=True)
13
- @click.option('--user_token', required=True)
14
- @click.option('--tenant', required=True)
15
- @click.option('--debug_modules', default='', envvar='SYNAPSE_DEBUG_MODULES')
16
- @click.pass_context
17
- def publish(ctx, host, user_token, tenant, debug_modules):
18
- debug = ctx.obj['DEBUG']
19
-
11
+ def _publish(host, access_token, debug, debug_modules=''):
12
+ """
13
+ Publish a plugin release to the Synapse backend.
14
+
15
+ :param host: The host URL of the Synapse backend.
16
+ :param access_token: The access token for authentication.
17
+ :param debug_modules: Comma-separated list of debug modules.
18
+ """
20
19
  plugin_release = PluginRelease()
21
20
 
22
21
  source_path = Path('./')
@@ -28,10 +27,19 @@ def publish(ctx, host, user_token, tenant, debug_modules):
28
27
  modules = debug_modules.split(',') if debug_modules else []
29
28
  data['meta'] = {'modules': modules}
30
29
 
31
- client = BackendClient(host, user_token, tenant=tenant)
30
+ client = BackendClient(host, access_token)
32
31
  client.create_plugin_release(data)
33
- click.secho(
34
- _('Successfully published "{}" ({}) to synapse backend!').format(plugin_release.name, plugin_release.code),
35
- fg='green',
36
- bold=True,
37
- )
32
+
33
+ message = _('Successfully published "{}" ({}) to synapse backend!').format(plugin_release.name, plugin_release.code)
34
+ click.secho(message, fg='green', bold=True)
35
+ return plugin_release
36
+
37
+
38
+ @click.command()
39
+ @click.option('--host', required=True)
40
+ @click.option('--access_token', required=True)
41
+ @click.option('--debug_modules', default='', envvar='SYNAPSE_DEBUG_MODULES')
42
+ @click.pass_context
43
+ def publish(ctx, host, access_token, debug_modules):
44
+ debug = ctx.obj['DEBUG']
45
+ _publish(host, access_token, debug, debug_modules)
@@ -1,18 +1,24 @@
1
+ from synapse_sdk.clients.agent.container import ContainerClientMixin
1
2
  from synapse_sdk.clients.agent.core import CoreClientMixin
2
3
  from synapse_sdk.clients.agent.ray import RayClientMixin
3
4
  from synapse_sdk.clients.agent.service import ServiceClientMixin
4
5
  from synapse_sdk.clients.exceptions import ClientError
5
6
 
6
7
 
7
- class AgentClient(CoreClientMixin, RayClientMixin, ServiceClientMixin):
8
+ class AgentClient(CoreClientMixin, RayClientMixin, ServiceClientMixin, ContainerClientMixin):
8
9
  name = 'Agent'
9
10
  agent_token = None
10
11
  user_token = None
11
12
  tenant = None
12
13
  long_poll_handler = None
13
14
 
14
- def __init__(self, base_url, agent_token, user_token=None, tenant=None, long_poll_handler=None):
15
- super().__init__(base_url)
15
+ def __init__(self, base_url, agent_token, user_token=None, tenant=None, long_poll_handler=None, timeout=None):
16
+ # Use shorter timeouts for agent connections for better UX
17
+ agent_timeout = timeout or {
18
+ 'connect': 3, # Connection timeout: 3 seconds
19
+ 'read': 10, # Read timeout: 10 seconds
20
+ }
21
+ super().__init__(base_url, timeout=agent_timeout)
16
22
  self.agent_token = agent_token
17
23
  self.user_token = user_token
18
24
  self.tenant = tenant