synapse-sdk 1.0.0b3__py3-none-any.whl → 1.0.0b5__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.
- synapse_sdk/cli/__init__.py +1 -1
- synapse_sdk/cli/code_server.py +319 -73
- synapse_sdk/clients/agent/core.py +15 -0
- synapse_sdk/plugins/categories/export/actions/export.py +11 -1
- synapse_sdk/plugins/categories/pre_annotation/actions/to_task.py +67 -13
- synapse_sdk/plugins/categories/upload/actions/upload.py +5 -8
- synapse_sdk/utils/encryption.py +158 -0
- {synapse_sdk-1.0.0b3.dist-info → synapse_sdk-1.0.0b5.dist-info}/METADATA +1 -1
- {synapse_sdk-1.0.0b3.dist-info → synapse_sdk-1.0.0b5.dist-info}/RECORD +13 -12
- {synapse_sdk-1.0.0b3.dist-info → synapse_sdk-1.0.0b5.dist-info}/WHEEL +0 -0
- {synapse_sdk-1.0.0b3.dist-info → synapse_sdk-1.0.0b5.dist-info}/entry_points.txt +0 -0
- {synapse_sdk-1.0.0b3.dist-info → synapse_sdk-1.0.0b5.dist-info}/licenses/LICENSE +0 -0
- {synapse_sdk-1.0.0b3.dist-info → synapse_sdk-1.0.0b5.dist-info}/top_level.txt +0 -0
synapse_sdk/cli/__init__.py
CHANGED
|
@@ -273,7 +273,7 @@ def cli(ctx, dev_tools):
|
|
|
273
273
|
message='Select an option:',
|
|
274
274
|
choices=[
|
|
275
275
|
('🌐 Run Dev Tools', 'devtools'),
|
|
276
|
-
('💻 Code-Server IDE', 'code_server'),
|
|
276
|
+
('💻 Open Code-Server IDE', 'code_server'),
|
|
277
277
|
('⚙️ Configuration', 'config'),
|
|
278
278
|
('🔌 Plugin Management', 'plugin'),
|
|
279
279
|
('🚪 Exit', 'exit'),
|
synapse_sdk/cli/code_server.py
CHANGED
|
@@ -1,26 +1,37 @@
|
|
|
1
1
|
"""Code-server integration for remote plugin development."""
|
|
2
2
|
|
|
3
|
+
import os
|
|
4
|
+
import shutil
|
|
5
|
+
import subprocess
|
|
6
|
+
from pathlib import Path
|
|
3
7
|
from typing import Optional
|
|
8
|
+
from urllib.parse import quote
|
|
4
9
|
|
|
5
10
|
import click
|
|
11
|
+
import inquirer
|
|
12
|
+
import yaml
|
|
6
13
|
|
|
7
14
|
from synapse_sdk.cli.config import fetch_agents_from_backend, get_agent_config
|
|
8
15
|
from synapse_sdk.devtools.config import get_backend_config
|
|
16
|
+
from synapse_sdk.utils.encryption import encrypt_plugin, get_plugin_info, is_plugin_directory
|
|
9
17
|
|
|
10
18
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
@click.option('--open-browser/--no-open-browser', default=True, help='Open in browser')
|
|
14
|
-
def code_server(agent: Optional[str], open_browser: bool):
|
|
15
|
-
"""Connect to web-based code-server on an agent for plugin development."""
|
|
19
|
+
def get_agent_client(agent: Optional[str] = None):
|
|
20
|
+
"""Helper function to get an agent client.
|
|
16
21
|
|
|
22
|
+
Args:
|
|
23
|
+
agent: Optional agent ID. If not provided, uses current agent or prompts user.
|
|
24
|
+
|
|
25
|
+
Returns:
|
|
26
|
+
tuple: (AgentClient instance, agent_id) or (None, None) if failed
|
|
27
|
+
"""
|
|
17
28
|
# Get current agent configuration
|
|
18
29
|
agent_config = get_agent_config()
|
|
19
30
|
backend_config = get_backend_config()
|
|
20
31
|
|
|
21
32
|
if not backend_config:
|
|
22
33
|
click.echo("❌ No backend configured. Run 'synapse config' first.")
|
|
23
|
-
return
|
|
34
|
+
return None, None
|
|
24
35
|
|
|
25
36
|
# If no agent specified, use current agent or let user choose
|
|
26
37
|
if not agent:
|
|
@@ -32,7 +43,7 @@ def code_server(agent: Optional[str], open_browser: bool):
|
|
|
32
43
|
agents, error = fetch_agents_from_backend()
|
|
33
44
|
if not agents:
|
|
34
45
|
click.echo('❌ No agents available. Check your backend configuration.')
|
|
35
|
-
return
|
|
46
|
+
return None, None
|
|
36
47
|
|
|
37
48
|
if len(agents) == 1:
|
|
38
49
|
# If only one agent, use it
|
|
@@ -52,14 +63,13 @@ def code_server(agent: Optional[str], open_browser: bool):
|
|
|
52
63
|
agent = agents[choice - 1]['id']
|
|
53
64
|
else:
|
|
54
65
|
click.echo('❌ Invalid selection')
|
|
55
|
-
return
|
|
66
|
+
return None, None
|
|
56
67
|
except (ValueError, EOFError, KeyboardInterrupt):
|
|
57
68
|
click.echo('\n❌ Cancelled')
|
|
58
|
-
return
|
|
69
|
+
return None, None
|
|
59
70
|
|
|
60
|
-
#
|
|
71
|
+
# Get agent details from backend
|
|
61
72
|
try:
|
|
62
|
-
# Get agent details from backend to get the agent URL
|
|
63
73
|
from synapse_sdk.clients.backend import BackendClient
|
|
64
74
|
|
|
65
75
|
backend_client = BackendClient(backend_config['host'], access_token=backend_config['token'])
|
|
@@ -70,100 +80,336 @@ def code_server(agent: Optional[str], open_browser: bool):
|
|
|
70
80
|
except Exception as e:
|
|
71
81
|
click.echo(f'❌ Failed to get agent information for: {agent}')
|
|
72
82
|
click.echo(f'Error: {e}')
|
|
73
|
-
return
|
|
83
|
+
return None, None
|
|
74
84
|
|
|
75
85
|
if not agent_info or not agent_info.get('url'):
|
|
76
86
|
click.echo(f'❌ Agent {agent} does not have a valid URL')
|
|
77
|
-
|
|
78
|
-
return
|
|
87
|
+
return None, None
|
|
79
88
|
|
|
80
89
|
# Get the agent token from local configuration
|
|
81
90
|
agent_token = agent_config.get('token')
|
|
82
91
|
if not agent_token:
|
|
83
92
|
click.echo('❌ No agent token found in configuration')
|
|
84
93
|
click.echo("Run 'synapse config' to configure the agent")
|
|
85
|
-
return
|
|
94
|
+
return None, None
|
|
86
95
|
|
|
87
96
|
# Create agent client
|
|
88
97
|
from synapse_sdk.clients.agent import AgentClient
|
|
89
98
|
|
|
90
99
|
client = AgentClient(base_url=agent_info['url'], agent_token=agent_token, user_token=backend_config['token'])
|
|
100
|
+
return client, agent
|
|
91
101
|
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
except AttributeError:
|
|
96
|
-
# Fallback to direct API call if method doesn't exist
|
|
97
|
-
response = client._get('code-server/info/')
|
|
98
|
-
info = response if isinstance(response, dict) else {}
|
|
99
|
-
except Exception as e:
|
|
100
|
-
# Handle other errors
|
|
101
|
-
click.echo(f'❌ Failed to get code-server info: {e}')
|
|
102
|
-
click.echo(f'Agent URL: {agent_info.get("url")}')
|
|
103
|
-
click.echo('\nNote: The agent might not have code-server endpoint implemented yet.')
|
|
104
|
-
return
|
|
102
|
+
except Exception as e:
|
|
103
|
+
click.echo(f'❌ Failed to connect to agent: {e}')
|
|
104
|
+
return None, None
|
|
105
105
|
|
|
106
|
-
if not info or not info.get('available', False):
|
|
107
|
-
message = info.get('message', 'Code-server is not available') if info else 'Failed to get code-server info'
|
|
108
|
-
click.echo(f'❌ {message}')
|
|
109
|
-
click.echo('\nTo enable code-server, reinstall the agent with code-server support.')
|
|
110
|
-
return
|
|
111
106
|
|
|
112
|
-
|
|
113
|
-
|
|
107
|
+
def detect_and_encrypt_plugin(workspace_path: str) -> Optional[dict]:
|
|
108
|
+
"""Detect and encrypt plugin code in the workspace.
|
|
114
109
|
|
|
115
|
-
|
|
110
|
+
Args:
|
|
111
|
+
workspace_path: Path to check for plugin
|
|
116
112
|
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
113
|
+
Returns:
|
|
114
|
+
dict: Encrypted plugin data or None if no plugin found
|
|
115
|
+
"""
|
|
116
|
+
plugin_path = Path(workspace_path)
|
|
117
|
+
|
|
118
|
+
if not is_plugin_directory(plugin_path):
|
|
119
|
+
return None
|
|
120
|
+
|
|
121
|
+
try:
|
|
122
|
+
plugin_info = get_plugin_info(plugin_path)
|
|
123
|
+
click.echo(f'🔍 Detected plugin: {plugin_info["name"]}')
|
|
124
|
+
|
|
125
|
+
if 'version' in plugin_info:
|
|
126
|
+
click.echo(f' Version: {plugin_info["version"]}')
|
|
127
|
+
if 'description' in plugin_info:
|
|
128
|
+
click.echo(f' Description: {plugin_info["description"]}')
|
|
129
|
+
|
|
130
|
+
click.echo('🔐 Encrypting plugin code...')
|
|
131
|
+
encrypted_package, password = encrypt_plugin(plugin_path)
|
|
132
|
+
|
|
133
|
+
# Add password to the package (in real implementation, this would be handled securely)
|
|
134
|
+
encrypted_package['password'] = password
|
|
135
|
+
|
|
136
|
+
click.echo('✅ Plugin code encrypted successfully')
|
|
137
|
+
return encrypted_package
|
|
138
|
+
|
|
139
|
+
except Exception as e:
|
|
140
|
+
click.echo(f'❌ Failed to encrypt plugin: {e}')
|
|
141
|
+
return None
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
def is_code_server_installed() -> bool:
|
|
145
|
+
"""Check if code-server is installed locally.
|
|
146
|
+
|
|
147
|
+
Returns:
|
|
148
|
+
bool: True if code-server is available in PATH
|
|
149
|
+
"""
|
|
150
|
+
return shutil.which('code-server') is not None
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
def get_code_server_port() -> int:
|
|
154
|
+
"""Get code-server port from config file.
|
|
125
155
|
|
|
126
|
-
|
|
156
|
+
Returns:
|
|
157
|
+
int: Port number from config, defaults to 8880 if not found
|
|
158
|
+
"""
|
|
159
|
+
config_path = Path.home() / '.config' / 'code-server' / 'config.yaml'
|
|
127
160
|
|
|
128
|
-
|
|
129
|
-
if
|
|
130
|
-
|
|
161
|
+
try:
|
|
162
|
+
if config_path.exists():
|
|
163
|
+
with open(config_path, 'r') as f:
|
|
164
|
+
config = yaml.safe_load(f)
|
|
165
|
+
|
|
166
|
+
# Parse bind-addr which can be in format "127.0.0.1:8880" or just ":8880"
|
|
167
|
+
bind_addr = config.get('bind-addr', '')
|
|
168
|
+
if ':' in bind_addr:
|
|
169
|
+
port_str = bind_addr.split(':')[-1]
|
|
170
|
+
try:
|
|
171
|
+
return int(port_str)
|
|
172
|
+
except ValueError:
|
|
173
|
+
pass
|
|
174
|
+
except Exception:
|
|
175
|
+
# If any error occurs reading config, fall back to default
|
|
176
|
+
pass
|
|
131
177
|
|
|
132
|
-
|
|
178
|
+
# Default port if config not found or invalid
|
|
179
|
+
return 8880
|
|
133
180
|
|
|
134
|
-
|
|
181
|
+
|
|
182
|
+
def launch_local_code_server(workspace_path: str, open_browser: bool = True) -> None:
|
|
183
|
+
"""Launch local code-server instance.
|
|
184
|
+
|
|
185
|
+
Args:
|
|
186
|
+
workspace_path: Directory to open in code-server
|
|
187
|
+
open_browser: Whether to open browser automatically
|
|
188
|
+
"""
|
|
189
|
+
try:
|
|
190
|
+
# Get port from config
|
|
191
|
+
port = get_code_server_port()
|
|
192
|
+
|
|
193
|
+
# Create URL with folder query parameter
|
|
194
|
+
encoded_path = quote(workspace_path)
|
|
195
|
+
url_with_folder = f'http://localhost:{port}/?folder={encoded_path}'
|
|
196
|
+
|
|
197
|
+
# Basic code-server command - let code-server handle the workspace internally
|
|
198
|
+
cmd = ['code-server', workspace_path]
|
|
199
|
+
|
|
200
|
+
if not open_browser:
|
|
201
|
+
cmd.append('--disable-getting-started-override')
|
|
202
|
+
|
|
203
|
+
click.echo(f'🚀 Starting local code-server for workspace: {workspace_path}')
|
|
204
|
+
click.echo(f' URL: {url_with_folder}')
|
|
205
|
+
click.echo(' Press Ctrl+C to stop the server')
|
|
206
|
+
|
|
207
|
+
# Start code-server in background if we need to open browser
|
|
208
|
+
if open_browser:
|
|
209
|
+
# Start code-server in background
|
|
210
|
+
import threading
|
|
211
|
+
import time
|
|
212
|
+
|
|
213
|
+
def start_server():
|
|
214
|
+
subprocess.run(cmd)
|
|
215
|
+
|
|
216
|
+
server_thread = threading.Thread(target=start_server, daemon=True)
|
|
217
|
+
server_thread.start()
|
|
218
|
+
|
|
219
|
+
# Give server a moment to start
|
|
220
|
+
click.echo(' Waiting for server to start...')
|
|
221
|
+
time.sleep(3)
|
|
222
|
+
|
|
223
|
+
# Open browser with folder parameter
|
|
135
224
|
try:
|
|
136
|
-
|
|
137
|
-
result = subprocess.run(['xdg-open', info['url']], capture_output=True, text=True, timeout=2)
|
|
225
|
+
result = subprocess.run(['xdg-open', url_with_folder], capture_output=True, text=True, timeout=2)
|
|
138
226
|
if result.returncode != 0:
|
|
139
227
|
click.echo('⚠️ Could not open browser automatically (no display?)')
|
|
140
|
-
click.echo(f'👉 Please manually open: {
|
|
228
|
+
click.echo(f'👉 Please manually open: {url_with_folder}')
|
|
229
|
+
else:
|
|
230
|
+
click.echo('✅ Browser opened successfully')
|
|
141
231
|
except (subprocess.TimeoutExpired, FileNotFoundError):
|
|
142
|
-
# xdg-open not available or timed out
|
|
143
232
|
click.echo('⚠️ Could not open browser (headless environment)')
|
|
144
|
-
click.echo(f'👉 Please manually open: {
|
|
233
|
+
click.echo(f'👉 Please manually open: {url_with_folder}')
|
|
145
234
|
except Exception:
|
|
146
|
-
|
|
147
|
-
|
|
235
|
+
click.echo(f'👉 Please manually open: {url_with_folder}')
|
|
236
|
+
|
|
237
|
+
# Wait for the server thread (blocking)
|
|
238
|
+
try:
|
|
239
|
+
server_thread.join()
|
|
240
|
+
except KeyboardInterrupt:
|
|
241
|
+
click.echo('\n\n✅ Code-server stopped')
|
|
242
|
+
else:
|
|
243
|
+
# Start code-server normally (blocking)
|
|
244
|
+
subprocess.run(cmd)
|
|
245
|
+
|
|
246
|
+
except KeyboardInterrupt:
|
|
247
|
+
click.echo('\n\n✅ Code-server stopped')
|
|
248
|
+
except Exception as e:
|
|
249
|
+
click.echo(f'❌ Failed to start local code-server: {e}')
|
|
250
|
+
|
|
251
|
+
|
|
252
|
+
def show_code_server_installation_help() -> None:
|
|
253
|
+
"""Show installation instructions for code-server."""
|
|
254
|
+
click.echo('\n❌ Code-server is not installed locally')
|
|
255
|
+
click.echo('\n📦 To install code-server, choose one of these options:')
|
|
256
|
+
click.echo('\n1. Install script (recommended):')
|
|
257
|
+
click.echo(' curl -fsSL https://code-server.dev/install.sh | sh')
|
|
258
|
+
click.echo('\n2. Using npm:')
|
|
259
|
+
click.echo(' npm install -g code-server')
|
|
260
|
+
click.echo('\n3. Using yarn:')
|
|
261
|
+
click.echo(' yarn global add code-server')
|
|
262
|
+
click.echo('\n4. Download from releases:')
|
|
263
|
+
click.echo(' https://github.com/coder/code-server/releases')
|
|
264
|
+
click.echo('\n📚 For more installation options, visit: https://coder.com/docs/code-server/latest/install')
|
|
265
|
+
|
|
148
266
|
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
click.echo('1. Open the URL in your browser')
|
|
152
|
-
click.echo('2. Enter the password if prompted')
|
|
153
|
-
click.echo('3. Start coding in the web-based VS Code!')
|
|
267
|
+
def run_agent_code_server(agent: Optional[str], workspace: str, open_browser: bool) -> None:
|
|
268
|
+
"""Run code-server through agent (existing functionality).
|
|
154
269
|
|
|
270
|
+
Args:
|
|
271
|
+
agent: Agent name or ID
|
|
272
|
+
workspace: Workspace directory path
|
|
273
|
+
open_browser: Whether to open browser automatically
|
|
274
|
+
"""
|
|
275
|
+
client, _ = get_agent_client(agent)
|
|
276
|
+
if not client:
|
|
277
|
+
return
|
|
278
|
+
|
|
279
|
+
# Check for plugin and show info if found
|
|
280
|
+
plugin_data = detect_and_encrypt_plugin(workspace)
|
|
281
|
+
if plugin_data:
|
|
282
|
+
click.echo('📦 Plugin detected and encrypted for secure transfer')
|
|
283
|
+
|
|
284
|
+
# Get code-server information
|
|
285
|
+
try:
|
|
286
|
+
info = client.get_code_server_info(workspace_path=workspace)
|
|
155
287
|
except Exception as e:
|
|
156
|
-
|
|
288
|
+
# Handle other errors
|
|
289
|
+
click.echo(f'❌ Failed to get code-server info: {e}')
|
|
290
|
+
click.echo('\nNote: The agent might not have code-server endpoint implemented yet.')
|
|
291
|
+
return
|
|
157
292
|
|
|
158
|
-
|
|
293
|
+
# Ensure info is a dictionary
|
|
294
|
+
if not isinstance(info, dict):
|
|
295
|
+
click.echo('❌ Invalid response from agent')
|
|
296
|
+
return
|
|
297
|
+
|
|
298
|
+
if not info.get('available', False):
|
|
299
|
+
message = info.get('message', 'Code-server is not available')
|
|
300
|
+
click.echo(f'❌ {message}')
|
|
301
|
+
click.echo('\nTo enable code-server, reinstall the agent with code-server support.')
|
|
302
|
+
return
|
|
303
|
+
|
|
304
|
+
# Display connection information
|
|
305
|
+
click.echo('\n✅ Code-Server is available!')
|
|
306
|
+
|
|
307
|
+
# Get the workspace path from response or use the requested one
|
|
308
|
+
actual_workspace = info.get('workspace', workspace)
|
|
309
|
+
|
|
310
|
+
# Show web browser access
|
|
311
|
+
click.echo('\n🌐 Web-based VS Code:')
|
|
312
|
+
url = info.get('url')
|
|
313
|
+
if not url:
|
|
314
|
+
click.echo('❌ No URL provided by agent')
|
|
315
|
+
return
|
|
316
|
+
|
|
317
|
+
click.echo(f' URL: {url}')
|
|
318
|
+
password = info.get('password')
|
|
319
|
+
if password:
|
|
320
|
+
click.echo(f' Password: {password}')
|
|
321
|
+
else:
|
|
322
|
+
click.echo(' Password: Not required (passwordless mode)')
|
|
323
|
+
|
|
324
|
+
# Show workspace information with better context
|
|
325
|
+
click.echo(f'\n📁 Agent Workspace: {actual_workspace}')
|
|
326
|
+
click.echo(f'📂 Local Project: {workspace}')
|
|
327
|
+
|
|
328
|
+
# Only show warning if the paths are drastically different and it's not the expected container path
|
|
329
|
+
if actual_workspace != workspace and not actual_workspace.startswith('/home/coder'):
|
|
330
|
+
click.echo(' ⚠️ Note: Agent workspace differs from local project path')
|
|
331
|
+
|
|
332
|
+
# Optionally open in browser
|
|
333
|
+
if open_browser and url:
|
|
334
|
+
click.echo('\nAttempting to open in browser...')
|
|
159
335
|
|
|
160
|
-
#
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
336
|
+
# Try to open browser, suppressing stderr to avoid xdg-open noise
|
|
337
|
+
try:
|
|
338
|
+
# Use subprocess to suppress xdg-open errors
|
|
339
|
+
result = subprocess.run(['xdg-open', url], capture_output=True, text=True, timeout=2)
|
|
340
|
+
if result.returncode != 0:
|
|
341
|
+
click.echo('⚠️ Could not open browser automatically (no display?)')
|
|
342
|
+
click.echo(f'👉 Please manually open: {url}')
|
|
343
|
+
except (subprocess.TimeoutExpired, FileNotFoundError):
|
|
344
|
+
# xdg-open not available or timed out
|
|
345
|
+
click.echo('⚠️ Could not open browser (headless environment)')
|
|
346
|
+
click.echo(f'👉 Please manually open: {url}')
|
|
347
|
+
except Exception:
|
|
348
|
+
# Fallback for other errors
|
|
349
|
+
click.echo(f'👉 Please manually open: {url}')
|
|
350
|
+
|
|
351
|
+
# Show additional instructions
|
|
352
|
+
click.echo('\n📝 Quick Start:')
|
|
353
|
+
click.echo('1. Open the URL in your browser')
|
|
354
|
+
click.echo('2. Enter the password if prompted')
|
|
355
|
+
click.echo('3. Start coding in the web-based VS Code!')
|
|
356
|
+
|
|
357
|
+
# Add note about workspace synchronization
|
|
358
|
+
if actual_workspace.startswith('/home/coder'):
|
|
359
|
+
click.echo("\n💡 Note: Your local project files will be available in the agent's workspace.")
|
|
360
|
+
click.echo(' Changes made in code-server will be reflected in your local project.')
|
|
361
|
+
|
|
362
|
+
|
|
363
|
+
@click.command()
|
|
364
|
+
@click.option('--agent', help='Agent name or ID')
|
|
365
|
+
@click.option('--open-browser/--no-open-browser', default=True, help='Open in browser')
|
|
366
|
+
@click.option('--workspace', help='Workspace directory path (defaults to current directory)')
|
|
367
|
+
def code_server(agent: Optional[str], open_browser: bool, workspace: Optional[str]):
|
|
368
|
+
"""Open code-server either through agent or locally."""
|
|
369
|
+
|
|
370
|
+
# Get current working directory if workspace not specified
|
|
371
|
+
if not workspace:
|
|
372
|
+
workspace = os.getcwd()
|
|
373
|
+
|
|
374
|
+
click.echo(f'Using workspace: {workspace}')
|
|
375
|
+
|
|
376
|
+
# Check if local code-server is available
|
|
377
|
+
local_available = is_code_server_installed()
|
|
378
|
+
|
|
379
|
+
# Create menu options based on availability
|
|
380
|
+
choices = []
|
|
164
381
|
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
382
|
+
# Always offer agent option
|
|
383
|
+
choices.append(('Open code-server through agent', 'agent'))
|
|
384
|
+
|
|
385
|
+
# Add local option if available
|
|
386
|
+
if local_available:
|
|
387
|
+
choices.append(('Open local code-server', 'local'))
|
|
388
|
+
else:
|
|
389
|
+
choices.append(('Install local code-server (not installed)', 'install'))
|
|
390
|
+
|
|
391
|
+
choices.append(('Cancel', 'cancel'))
|
|
392
|
+
|
|
393
|
+
# Show selection menu
|
|
394
|
+
questions = [inquirer.List('option', message='How would you like to open code-server?', choices=choices)]
|
|
395
|
+
|
|
396
|
+
try:
|
|
397
|
+
answers = inquirer.prompt(questions)
|
|
398
|
+
if not answers or answers['option'] == 'cancel':
|
|
399
|
+
click.echo('Cancelled')
|
|
400
|
+
return
|
|
401
|
+
|
|
402
|
+
if answers['option'] == 'agent':
|
|
403
|
+
click.echo('\n🤖 Opening code-server through agent...')
|
|
404
|
+
run_agent_code_server(agent, workspace, open_browser)
|
|
405
|
+
|
|
406
|
+
elif answers['option'] == 'local':
|
|
407
|
+
click.echo('\n💻 Starting local code-server...')
|
|
408
|
+
launch_local_code_server(workspace, open_browser)
|
|
409
|
+
|
|
410
|
+
elif answers['option'] == 'install':
|
|
411
|
+
show_code_server_installation_help()
|
|
412
|
+
|
|
413
|
+
except (KeyboardInterrupt, EOFError):
|
|
414
|
+
click.echo('\n\nCancelled')
|
|
415
|
+
return
|
|
@@ -9,3 +9,18 @@ class CoreClientMixin(BaseClient):
|
|
|
9
9
|
def get_metrics(self, panel):
|
|
10
10
|
path = f'metrics/{panel}/'
|
|
11
11
|
return self._get(path)
|
|
12
|
+
|
|
13
|
+
def get_code_server_info(self, workspace_path=None):
|
|
14
|
+
"""Get code-server connection information from the agent.
|
|
15
|
+
|
|
16
|
+
Args:
|
|
17
|
+
workspace_path: Optional path to set as the workspace directory
|
|
18
|
+
|
|
19
|
+
Returns:
|
|
20
|
+
dict: Code-server connection information
|
|
21
|
+
"""
|
|
22
|
+
path = 'code-server/info/'
|
|
23
|
+
params = {}
|
|
24
|
+
if workspace_path:
|
|
25
|
+
params['workspace'] = workspace_path
|
|
26
|
+
return self._get(path, params=params)
|
|
@@ -40,7 +40,7 @@ class ExportRun(Run):
|
|
|
40
40
|
"""Log export file information.
|
|
41
41
|
|
|
42
42
|
Args:
|
|
43
|
-
log_type (str): The type of log ('export_data_file' or 'export_original_file').
|
|
43
|
+
log_type (str): The type of log ('export_data_file' or 'export_original_file' or 'etc').
|
|
44
44
|
target_id (int): The ID of the data file.
|
|
45
45
|
data_file_info (dict): The JSON info of the data file.
|
|
46
46
|
status (ExportStatus): The status of the data file.
|
|
@@ -88,6 +88,16 @@ class ExportRun(Run):
|
|
|
88
88
|
"""Log export origin data file."""
|
|
89
89
|
self.log_file('export_original_file', target_id, data_file_info, status, error)
|
|
90
90
|
|
|
91
|
+
def export_log_etc_file(
|
|
92
|
+
self,
|
|
93
|
+
target_id: int,
|
|
94
|
+
data_file_info: dict,
|
|
95
|
+
status: ExportStatus = ExportStatus.STAND_BY,
|
|
96
|
+
error: str | None = None,
|
|
97
|
+
):
|
|
98
|
+
"""Log export etc file."""
|
|
99
|
+
self.log_file('etc', target_id, data_file_info, status, error)
|
|
100
|
+
|
|
91
101
|
|
|
92
102
|
class ExportTargetHandler(ABC):
|
|
93
103
|
"""
|
|
@@ -8,6 +8,7 @@ from pydantic import AfterValidator, BaseModel, field_validator
|
|
|
8
8
|
from pydantic_core import PydanticCustomError
|
|
9
9
|
|
|
10
10
|
from synapse_sdk.clients.backend import BackendClient
|
|
11
|
+
from synapse_sdk.clients.backend.models import JobStatus
|
|
11
12
|
from synapse_sdk.clients.exceptions import ClientError
|
|
12
13
|
from synapse_sdk.plugins.categories.base import Action
|
|
13
14
|
from synapse_sdk.plugins.categories.decorators import register_action
|
|
@@ -35,6 +36,14 @@ class CriticalError(Exception):
|
|
|
35
36
|
super().__init__(self.message)
|
|
36
37
|
|
|
37
38
|
|
|
39
|
+
class PreAnnotationToTaskFailed(Exception):
|
|
40
|
+
"""Pre-annotation to task failed."""
|
|
41
|
+
|
|
42
|
+
def __init__(self, message: str = 'Pre-annotation to task failed'):
|
|
43
|
+
self.message = message
|
|
44
|
+
super().__init__(self.message)
|
|
45
|
+
|
|
46
|
+
|
|
38
47
|
class ToTaskRun(Run):
|
|
39
48
|
class AnnotateTaskEventLog(BaseModel):
|
|
40
49
|
"""Annotate task event log model."""
|
|
@@ -255,6 +264,25 @@ class ToTaskParams(BaseModel):
|
|
|
255
264
|
return value
|
|
256
265
|
|
|
257
266
|
|
|
267
|
+
class ToTaskResult(BaseModel):
|
|
268
|
+
"""Result model for ToTaskAction.start method.
|
|
269
|
+
|
|
270
|
+
Args:
|
|
271
|
+
status (JobStatus): The job status from the action execution.
|
|
272
|
+
message (str): A descriptive message about the action result.
|
|
273
|
+
"""
|
|
274
|
+
|
|
275
|
+
status: JobStatus
|
|
276
|
+
message: str
|
|
277
|
+
|
|
278
|
+
def model_dump(self, **kwargs):
|
|
279
|
+
"""Override model_dump to return status as enum value."""
|
|
280
|
+
data = super().model_dump(**kwargs)
|
|
281
|
+
if 'status' in data and isinstance(data['status'], JobStatus):
|
|
282
|
+
data['status'] = data['status'].value
|
|
283
|
+
return data
|
|
284
|
+
|
|
285
|
+
|
|
258
286
|
@register_action
|
|
259
287
|
class ToTaskAction(Action):
|
|
260
288
|
"""ToTask action for pre-annotation data processing.
|
|
@@ -306,14 +334,20 @@ class ToTaskAction(Action):
|
|
|
306
334
|
}
|
|
307
335
|
}
|
|
308
336
|
|
|
309
|
-
def start(self):
|
|
337
|
+
def start(self) -> dict:
|
|
310
338
|
"""Start to_task action.
|
|
311
339
|
|
|
312
340
|
* Generate tasks.
|
|
313
341
|
* Annotate data to tasks.
|
|
342
|
+
|
|
343
|
+
Returns:
|
|
344
|
+
dict: Validated result with status and message.
|
|
314
345
|
"""
|
|
315
346
|
if not self.run or not self.params:
|
|
316
|
-
|
|
347
|
+
result = ToTaskResult(
|
|
348
|
+
status=JobStatus.FAILED, message='Run instance or parameters not properly initialized'
|
|
349
|
+
)
|
|
350
|
+
raise PreAnnotationToTaskFailed(result.message)
|
|
317
351
|
|
|
318
352
|
# Type assertion to help the linter
|
|
319
353
|
assert isinstance(self.run, ToTaskRun)
|
|
@@ -325,20 +359,23 @@ class ToTaskAction(Action):
|
|
|
325
359
|
if isinstance(project_response, str):
|
|
326
360
|
self.run.log_message_with_code('INVALID_PROJECT_RESPONSE')
|
|
327
361
|
self.run.end_log()
|
|
328
|
-
|
|
362
|
+
result = ToTaskResult(status=JobStatus.FAILED, message='Invalid project response received')
|
|
363
|
+
raise PreAnnotationToTaskFailed(result.message)
|
|
329
364
|
project: Dict[str, Any] = project_response
|
|
330
365
|
|
|
331
366
|
data_collection_id = project.get('data_collection')
|
|
332
367
|
if not data_collection_id:
|
|
333
368
|
self.run.log_message_with_code('NO_DATA_COLLECTION')
|
|
334
369
|
self.run.end_log()
|
|
335
|
-
|
|
370
|
+
result = ToTaskResult(status=JobStatus.FAILED, message='Project does not have a data collection')
|
|
371
|
+
raise PreAnnotationToTaskFailed(result.message)
|
|
336
372
|
|
|
337
373
|
data_collection_response = client.get_data_collection(data_collection_id)
|
|
338
374
|
if isinstance(data_collection_response, str):
|
|
339
375
|
self.run.log_message_with_code('INVALID_DATA_COLLECTION_RESPONSE')
|
|
340
376
|
self.run.end_log()
|
|
341
|
-
|
|
377
|
+
result = ToTaskResult(status=JobStatus.FAILED, message='Invalid data collection response received')
|
|
378
|
+
raise PreAnnotationToTaskFailed(result.message)
|
|
342
379
|
data_collection: Dict[str, Any] = data_collection_response
|
|
343
380
|
|
|
344
381
|
# Generate tasks if provided project is empty.
|
|
@@ -355,7 +392,8 @@ class ToTaskAction(Action):
|
|
|
355
392
|
if not task_ids_count:
|
|
356
393
|
self.run.log_message_with_code('NO_TASKS_FOUND')
|
|
357
394
|
self.run.end_log()
|
|
358
|
-
|
|
395
|
+
result = ToTaskResult(status=JobStatus.FAILED, message='No tasks found to annotate')
|
|
396
|
+
raise PreAnnotationToTaskFailed(result.message)
|
|
359
397
|
|
|
360
398
|
# Annotate data to tasks.
|
|
361
399
|
method = self.params.get('method')
|
|
@@ -365,23 +403,39 @@ class ToTaskAction(Action):
|
|
|
365
403
|
if not target_specification_name:
|
|
366
404
|
self.run.log_message_with_code('TARGET_SPEC_REQUIRED')
|
|
367
405
|
self.run.end_log()
|
|
368
|
-
|
|
406
|
+
result = ToTaskResult(
|
|
407
|
+
status=JobStatus.FAILED, message='Target specification name is required for file annotation method'
|
|
408
|
+
)
|
|
409
|
+
raise PreAnnotationToTaskFailed(result.message)
|
|
369
410
|
|
|
370
411
|
file_specifications = data_collection.get('file_specifications', [])
|
|
371
412
|
target_spec_exists = any(spec.get('name') == target_specification_name for spec in file_specifications)
|
|
372
413
|
if not target_spec_exists:
|
|
373
414
|
self.run.log_message_with_code('TARGET_SPEC_NOT_FOUND', target_specification_name)
|
|
374
415
|
self.run.end_log()
|
|
375
|
-
|
|
416
|
+
result = ToTaskResult(
|
|
417
|
+
status=JobStatus.FAILED,
|
|
418
|
+
message=f"Target specification name '{target_specification_name}' not found in file specifications",
|
|
419
|
+
)
|
|
420
|
+
raise PreAnnotationToTaskFailed(result.message)
|
|
376
421
|
self._handle_annotate_data_from_files(task_ids, target_specification_name)
|
|
377
422
|
elif method == AnnotationMethod.INFERENCE:
|
|
378
423
|
self._handle_annotate_data_with_inference(task_ids)
|
|
379
424
|
else:
|
|
380
425
|
self.run.log_message_with_code('UNSUPPORTED_METHOD', method)
|
|
381
426
|
self.run.end_log()
|
|
382
|
-
|
|
427
|
+
result = ToTaskResult(status=JobStatus.FAILED, message=f'Unsupported annotation method: {method}')
|
|
428
|
+
raise PreAnnotationToTaskFailed(result.message)
|
|
429
|
+
|
|
430
|
+
current_progress = self.run.logger.get_current_progress()
|
|
431
|
+
if current_progress['overall'] != 100:
|
|
432
|
+
result = ToTaskResult(
|
|
433
|
+
status=JobStatus.FAILED, message='Pre-annotation to task failed. Current progress is not 100%'
|
|
434
|
+
)
|
|
435
|
+
raise PreAnnotationToTaskFailed(result.message)
|
|
383
436
|
|
|
384
|
-
|
|
437
|
+
result = ToTaskResult(status=JobStatus.SUCCEEDED, message='Pre-annotation to task completed successfully')
|
|
438
|
+
return result.model_dump()
|
|
385
439
|
|
|
386
440
|
def _handle_annotate_data_from_files(self, task_ids: List[int], target_specification_name: str):
|
|
387
441
|
"""Handle annotate data from files to tasks.
|
|
@@ -757,21 +811,21 @@ class ToTaskAction(Action):
|
|
|
757
811
|
except Exception as e:
|
|
758
812
|
return {'success': False, 'error': f'Failed to restart pre-processor: {str(e)}'}
|
|
759
813
|
|
|
760
|
-
def _extract_primary_file_url(self, task: Dict[str, Any]) -> Tuple[str, str]:
|
|
814
|
+
def _extract_primary_file_url(self, task: Dict[str, Any]) -> Tuple[Optional[str], Optional[str]]:
|
|
761
815
|
"""Extract the primary file URL from task data.
|
|
762
816
|
|
|
763
817
|
Args:
|
|
764
818
|
task (Dict[str, Any]): The task data.
|
|
765
819
|
|
|
766
820
|
Returns:
|
|
767
|
-
Tuple[str, str]: The primary file URL and original name.
|
|
821
|
+
Tuple[Optional[str], Optional[str]]: The primary file URL and original name.
|
|
768
822
|
"""
|
|
769
823
|
data_unit = task.get('data_unit', {})
|
|
770
824
|
files = data_unit.get('files', {})
|
|
771
825
|
|
|
772
826
|
for file_info in files.values():
|
|
773
827
|
if isinstance(file_info, dict) and file_info.get('is_primary') and file_info.get('url'):
|
|
774
|
-
return file_info['url'], file_info
|
|
828
|
+
return file_info['url'], file_info.get('file_name_original')
|
|
775
829
|
|
|
776
830
|
return None, None
|
|
777
831
|
|
|
@@ -18,6 +18,7 @@ from synapse_sdk.i18n import gettext as _
|
|
|
18
18
|
from synapse_sdk.plugins.categories.base import Action
|
|
19
19
|
from synapse_sdk.plugins.categories.decorators import register_action
|
|
20
20
|
from synapse_sdk.plugins.enums import PluginCategory, RunMethod
|
|
21
|
+
from synapse_sdk.plugins.exceptions import ActionError
|
|
21
22
|
from synapse_sdk.plugins.models import Run
|
|
22
23
|
from synapse_sdk.shared.enums import Context
|
|
23
24
|
from synapse_sdk.utils.pydantic.validators import non_blank
|
|
@@ -684,30 +685,26 @@ class UploadAction(Action):
|
|
|
684
685
|
# Validate the organized files
|
|
685
686
|
if not self._validate_organized_files(organized_files, file_specification_template):
|
|
686
687
|
self.run.log_message('Validation failed.', context=Context.ERROR.value)
|
|
687
|
-
|
|
688
|
-
return result
|
|
688
|
+
raise ActionError('Upload is aborted due to validation errors.')
|
|
689
689
|
|
|
690
690
|
# Upload files to synapse-backend.
|
|
691
691
|
if not organized_files:
|
|
692
692
|
self.run.log_message('Files not found on the path.', context=Context.WARNING.value)
|
|
693
|
-
|
|
694
|
-
return result
|
|
693
|
+
raise ActionError('Upload is aborted due to missing files.')
|
|
695
694
|
uploaded_files = self._upload_files(organized_files)
|
|
696
695
|
result['uploaded_files_count'] = len(uploaded_files)
|
|
697
696
|
|
|
698
697
|
# Generate data units for the uploaded data.
|
|
699
698
|
if not uploaded_files:
|
|
700
699
|
self.run.log_message('No files were uploaded.', context=Context.WARNING.value)
|
|
701
|
-
|
|
702
|
-
return result
|
|
700
|
+
raise ActionError('Upload is aborted due to no uploaded files.')
|
|
703
701
|
generated_data_units = self._generate_data_units(uploaded_files)
|
|
704
702
|
result['generated_data_units_count'] = len(generated_data_units)
|
|
705
703
|
|
|
706
704
|
# Setup task with uploaded synapse-backend data units.
|
|
707
705
|
if not generated_data_units:
|
|
708
706
|
self.run.log_message('No data units were generated.', context=Context.WARNING.value)
|
|
709
|
-
|
|
710
|
-
return result
|
|
707
|
+
raise ActionError('Upload is aborted due to no generated data units.')
|
|
711
708
|
|
|
712
709
|
self.run.log_message('Import completed.')
|
|
713
710
|
return result
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
"""Encryption utilities for plugin code security."""
|
|
2
|
+
|
|
3
|
+
import base64
|
|
4
|
+
import os
|
|
5
|
+
import zipfile
|
|
6
|
+
from io import BytesIO
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
from typing import Dict, List, Optional, Tuple
|
|
9
|
+
|
|
10
|
+
from cryptography.fernet import Fernet
|
|
11
|
+
from cryptography.hazmat.primitives import hashes
|
|
12
|
+
from cryptography.hazmat.primitives.kdf.pbkdf2 import PBKDF2HMAC
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def generate_key(password: str, salt: bytes) -> bytes:
|
|
16
|
+
"""Generate encryption key from password.
|
|
17
|
+
|
|
18
|
+
* Argument iterations should be at least 500,000.
|
|
19
|
+
"""
|
|
20
|
+
kdf = PBKDF2HMAC(
|
|
21
|
+
algorithm=hashes.SHA256(),
|
|
22
|
+
length=32,
|
|
23
|
+
salt=salt,
|
|
24
|
+
iterations=700000,
|
|
25
|
+
)
|
|
26
|
+
return base64.urlsafe_b64encode(kdf.derive(password.encode()))
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def encrypt_data(data: bytes, password: str) -> Dict:
|
|
30
|
+
"""Encrypt data with password."""
|
|
31
|
+
salt = os.urandom(16)
|
|
32
|
+
key = generate_key(password, salt)
|
|
33
|
+
fernet = Fernet(key)
|
|
34
|
+
encrypted_data = fernet.encrypt(data)
|
|
35
|
+
|
|
36
|
+
return {
|
|
37
|
+
'encrypted_data': base64.b64encode(encrypted_data).decode(),
|
|
38
|
+
'salt': base64.b64encode(salt).decode(),
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def decrypt_data(encrypted_package: Dict, password: str) -> bytes:
|
|
43
|
+
"""Decrypt data with password."""
|
|
44
|
+
salt = base64.b64decode(encrypted_package['salt'])
|
|
45
|
+
encrypted_data = base64.b64decode(encrypted_package['encrypted_data'])
|
|
46
|
+
|
|
47
|
+
key = generate_key(password, salt)
|
|
48
|
+
fernet = Fernet(key)
|
|
49
|
+
return fernet.decrypt(encrypted_data)
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def is_plugin_directory(path: Path) -> bool:
|
|
53
|
+
"""Check if directory contains a Synapse plugin."""
|
|
54
|
+
config_file = path / 'config.yaml'
|
|
55
|
+
plugin_dir = path / 'plugin'
|
|
56
|
+
|
|
57
|
+
return config_file.exists() and plugin_dir.exists() and plugin_dir.is_dir()
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def get_plugin_files(plugin_path: Path) -> List[Tuple[Path, str]]:
|
|
61
|
+
"""Get all plugin files with their relative paths."""
|
|
62
|
+
plugin_files = []
|
|
63
|
+
|
|
64
|
+
# Essential plugin files
|
|
65
|
+
essential_patterns = [
|
|
66
|
+
'config.yaml',
|
|
67
|
+
'requirements.txt',
|
|
68
|
+
'README.md',
|
|
69
|
+
'pyproject.toml',
|
|
70
|
+
]
|
|
71
|
+
|
|
72
|
+
for pattern in essential_patterns:
|
|
73
|
+
file_path = plugin_path / pattern
|
|
74
|
+
if file_path.exists():
|
|
75
|
+
plugin_files.append((file_path, pattern))
|
|
76
|
+
|
|
77
|
+
# Plugin source code
|
|
78
|
+
plugin_dir = plugin_path / 'plugin'
|
|
79
|
+
if plugin_dir.exists():
|
|
80
|
+
for file_path in plugin_dir.rglob('*'):
|
|
81
|
+
if file_path.is_file() and not file_path.name.startswith('.'):
|
|
82
|
+
relative_path = f'plugin/{file_path.relative_to(plugin_dir)}'
|
|
83
|
+
plugin_files.append((file_path, relative_path))
|
|
84
|
+
|
|
85
|
+
# Additional common directories
|
|
86
|
+
for additional_dir in ['tests', 'docs', 'data']:
|
|
87
|
+
dir_path = plugin_path / additional_dir
|
|
88
|
+
if dir_path.exists() and dir_path.is_dir():
|
|
89
|
+
for file_path in dir_path.rglob('*'):
|
|
90
|
+
if file_path.is_file() and not file_path.name.startswith('.'):
|
|
91
|
+
relative_path = f'{additional_dir}/{file_path.relative_to(dir_path)}'
|
|
92
|
+
plugin_files.append((file_path, relative_path))
|
|
93
|
+
|
|
94
|
+
return plugin_files
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
def create_plugin_archive(plugin_path: Path) -> bytes:
|
|
98
|
+
"""Create a zip archive of the plugin."""
|
|
99
|
+
plugin_files = get_plugin_files(plugin_path)
|
|
100
|
+
|
|
101
|
+
archive_buffer = BytesIO()
|
|
102
|
+
with zipfile.ZipFile(archive_buffer, 'w', zipfile.ZIP_DEFLATED) as zip_file:
|
|
103
|
+
for file_path, archive_path in plugin_files:
|
|
104
|
+
zip_file.write(file_path, archive_path)
|
|
105
|
+
|
|
106
|
+
return archive_buffer.getvalue()
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
def encrypt_plugin(plugin_path: Path, password: Optional[str] = None) -> Dict:
|
|
110
|
+
"""Encrypt a plugin directory."""
|
|
111
|
+
if not is_plugin_directory(plugin_path):
|
|
112
|
+
raise ValueError(f'Directory {plugin_path} is not a valid plugin directory')
|
|
113
|
+
|
|
114
|
+
# Generate password if not provided
|
|
115
|
+
if password is None:
|
|
116
|
+
password = base64.urlsafe_b64encode(os.urandom(32)).decode()
|
|
117
|
+
|
|
118
|
+
# Create plugin archive
|
|
119
|
+
archive_data = create_plugin_archive(plugin_path)
|
|
120
|
+
|
|
121
|
+
# Encrypt the archive
|
|
122
|
+
encrypted_package = encrypt_data(archive_data, password)
|
|
123
|
+
|
|
124
|
+
# Add metadata
|
|
125
|
+
encrypted_package.update({
|
|
126
|
+
'plugin_name': plugin_path.name,
|
|
127
|
+
'plugin_path': str(plugin_path),
|
|
128
|
+
'encryption_method': 'fernet',
|
|
129
|
+
'archive_format': 'zip',
|
|
130
|
+
})
|
|
131
|
+
|
|
132
|
+
return encrypted_package, password
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
def get_plugin_info(plugin_path: Path) -> Optional[Dict]:
|
|
136
|
+
"""Get basic plugin information."""
|
|
137
|
+
if not is_plugin_directory(plugin_path):
|
|
138
|
+
return None
|
|
139
|
+
|
|
140
|
+
info = {'name': plugin_path.name, 'path': str(plugin_path), 'is_plugin': True}
|
|
141
|
+
|
|
142
|
+
# Try to read config.yaml for additional info
|
|
143
|
+
config_file = plugin_path / 'config.yaml'
|
|
144
|
+
if config_file.exists():
|
|
145
|
+
try:
|
|
146
|
+
import yaml
|
|
147
|
+
|
|
148
|
+
with open(config_file, 'r', encoding='utf-8') as f:
|
|
149
|
+
config = yaml.safe_load(f)
|
|
150
|
+
info.update({
|
|
151
|
+
'version': config.get('version', 'unknown'),
|
|
152
|
+
'description': config.get('description', ''),
|
|
153
|
+
'category': config.get('category', 'unknown'),
|
|
154
|
+
})
|
|
155
|
+
except Exception:
|
|
156
|
+
pass # Continue without config details if parsing fails
|
|
157
|
+
|
|
158
|
+
return info
|
|
@@ -6,8 +6,8 @@ synapse_sdk/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
|
6
6
|
synapse_sdk/i18n.py,sha256=VXMR-Zm_1hTAg9iPk3YZNNq-T1Bhx1J2fEtRT6kyYbg,766
|
|
7
7
|
synapse_sdk/loggers.py,sha256=xK48h3ZaDDZLaF-qsdnv1-6-4vw_cYlgpSCKHYUQw1g,6549
|
|
8
8
|
synapse_sdk/types.py,sha256=khzn8KpgxFdn1SrpbcuX84m_Md1Mz_HIoUoPq8uok40,698
|
|
9
|
-
synapse_sdk/cli/__init__.py,sha256=
|
|
10
|
-
synapse_sdk/cli/code_server.py,sha256=
|
|
9
|
+
synapse_sdk/cli/__init__.py,sha256=64Qaxak5rwhEKUj5xLkdg1vtBgyl4Do1Z97eNiqNqEE,11668
|
|
10
|
+
synapse_sdk/cli/code_server.py,sha256=0AnyZSrHxfH7YU02PXPjcvekH-R1BRRrIA0NbA8Ckd8,15421
|
|
11
11
|
synapse_sdk/cli/config.py,sha256=2WVKeAdcDeiwRb4JnNEOeZoAVlTHrY2jx4NAUiAZO2c,16156
|
|
12
12
|
synapse_sdk/cli/devtools.py,sha256=hi06utdLllptlUy3HhPfmfRqjc9bdiVGezY0U5s_0pY,2617
|
|
13
13
|
synapse_sdk/cli/alias/__init__.py,sha256=jDy8N_KupVy7n_jKKWhjQOj76-mR-uoVvMoyzObUkuI,405
|
|
@@ -28,7 +28,7 @@ synapse_sdk/clients/base.py,sha256=2-aCbWx0LcFGXXgd9nkWtaaSroRJybjRAHAT4qCjhv0,1
|
|
|
28
28
|
synapse_sdk/clients/exceptions.py,sha256=ylv7x10eOp4aA3a48jwonnvqvkiYwzJYXjkVkRTAjwk,220
|
|
29
29
|
synapse_sdk/clients/utils.py,sha256=8pPJTdzHiRPSbZMoQYHAgR2BAMO6u_R_jMV6a2p34iQ,392
|
|
30
30
|
synapse_sdk/clients/agent/__init__.py,sha256=FqYbtzMJdzRfuU2SA-Yxdc0JKmVP1wxH6OlUNmB4lH8,2230
|
|
31
|
-
synapse_sdk/clients/agent/core.py,sha256=
|
|
31
|
+
synapse_sdk/clients/agent/core.py,sha256=aeMSzf8BF7LjVcmHaL8zC7ofBZUff8kIeqkW1xUJ6Sk,745
|
|
32
32
|
synapse_sdk/clients/agent/ray.py,sha256=1EDl-bMN2CvKl07-qMidSWNOGpvIvzcWl7jDBCza65o,3248
|
|
33
33
|
synapse_sdk/clients/agent/service.py,sha256=s7KuPK_DB1nr2VHrigttV1WyFonaGHNrPvU8loRxHcE,478
|
|
34
34
|
synapse_sdk/clients/backend/__init__.py,sha256=9FzjQn0ljRhtdaoG3n38Mdgte7GFwIh4OtEmoqVg2_E,2098
|
|
@@ -113,7 +113,7 @@ synapse_sdk/plugins/categories/data_validation/templates/plugin/validation.py,sh
|
|
|
113
113
|
synapse_sdk/plugins/categories/export/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
114
114
|
synapse_sdk/plugins/categories/export/enums.py,sha256=gtyngvQ1DKkos9iKGcbecwTVQQ6sDwbrBPSGPNb5Am0,127
|
|
115
115
|
synapse_sdk/plugins/categories/export/actions/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
116
|
-
synapse_sdk/plugins/categories/export/actions/export.py,sha256=
|
|
116
|
+
synapse_sdk/plugins/categories/export/actions/export.py,sha256=7OCU3kwkYqYnelG6vfZ4ClyyMj_FHT-WQoUmgcfH080,12012
|
|
117
117
|
synapse_sdk/plugins/categories/export/templates/config.yaml,sha256=N7YmnFROb3s3M35SA9nmabyzoSb5O2t2TRPicwFNN2o,56
|
|
118
118
|
synapse_sdk/plugins/categories/export/templates/plugin/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
119
119
|
synapse_sdk/plugins/categories/export/templates/plugin/export.py,sha256=GDb6Ucodsr5aBPMU4alr68-DyFoLR5TyhC_MCaJrkF0,6411
|
|
@@ -141,7 +141,7 @@ synapse_sdk/plugins/categories/post_annotation/templates/plugin/post_annotation.
|
|
|
141
141
|
synapse_sdk/plugins/categories/pre_annotation/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
142
142
|
synapse_sdk/plugins/categories/pre_annotation/actions/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
143
143
|
synapse_sdk/plugins/categories/pre_annotation/actions/pre_annotation.py,sha256=6ib3RmnGrjpsQ0e_G-mRH1lfFunQ3gh2M831vuDn7HU,344
|
|
144
|
-
synapse_sdk/plugins/categories/pre_annotation/actions/to_task.py,sha256=
|
|
144
|
+
synapse_sdk/plugins/categories/pre_annotation/actions/to_task.py,sha256=x6KPK89OWS7wsmYZ-wFFPhUl_fXEO74NIVB4bUChm6Q,40323
|
|
145
145
|
synapse_sdk/plugins/categories/pre_annotation/templates/config.yaml,sha256=VREoCp9wsvZ8T2E1d_MEKlR8TC_herDJGVQtu3ezAYU,589
|
|
146
146
|
synapse_sdk/plugins/categories/pre_annotation/templates/plugin/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
147
147
|
synapse_sdk/plugins/categories/pre_annotation/templates/plugin/pre_annotation.py,sha256=HBHxHuv2gMBzDB2alFfrzI_SZ1Ztk6mo7eFbR5GqHKw,106
|
|
@@ -154,7 +154,7 @@ synapse_sdk/plugins/categories/smart_tool/templates/plugin/__init__.py,sha256=47
|
|
|
154
154
|
synapse_sdk/plugins/categories/smart_tool/templates/plugin/auto_label.py,sha256=eevNg0nOcYFR4z_L_R-sCvVOYoLWSAH1jwDkAf3YCjY,320
|
|
155
155
|
synapse_sdk/plugins/categories/upload/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
156
156
|
synapse_sdk/plugins/categories/upload/actions/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
157
|
-
synapse_sdk/plugins/categories/upload/actions/upload.py,sha256=
|
|
157
|
+
synapse_sdk/plugins/categories/upload/actions/upload.py,sha256=Lhc_ezB_FvkDI7LTe50tadOQ2W9vp2ppTWYNJSWYKfY,38114
|
|
158
158
|
synapse_sdk/plugins/categories/upload/templates/config.yaml,sha256=6_dRa0_J2aS8NSUfO4MKbPxZcdPS2FpJzzp51edYAZc,281
|
|
159
159
|
synapse_sdk/plugins/categories/upload/templates/plugin/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
160
160
|
synapse_sdk/plugins/categories/upload/templates/plugin/upload.py,sha256=IZU4sdSMSLKPCtlNqF7DP2howTdYR6hr74HCUZsGdPk,1559
|
|
@@ -180,6 +180,7 @@ synapse_sdk/shared/enums.py,sha256=5uy4HGKtGCAvrBMuVSzwloJ6f41sOk0ty__605zF8hg,3
|
|
|
180
180
|
synapse_sdk/utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
181
181
|
synapse_sdk/utils/dataset.py,sha256=zWTzFmv589izFr62BDuApi3r5FpTsdm-5AmriC0AEdM,1865
|
|
182
182
|
synapse_sdk/utils/debug.py,sha256=F7JlUwYjTFZAMRbBqKm6hxOIz-_IXYA8lBInOS4jbS4,100
|
|
183
|
+
synapse_sdk/utils/encryption.py,sha256=KMARrAk5aIHfBLC8CvdXiSIuaGvxljluubjF9PVLf7c,5100
|
|
183
184
|
synapse_sdk/utils/file.py,sha256=wWBQAx0cB5a-fjfRMeJV-KjBil1ZyKRz-vXno3xBSoo,6834
|
|
184
185
|
synapse_sdk/utils/http.py,sha256=yRxYfru8tMnBVeBK-7S0Ga13yOf8oRHquG5e8K_FWcI,4759
|
|
185
186
|
synapse_sdk/utils/module_loading.py,sha256=chHpU-BZjtYaTBD_q0T7LcKWtqKvYBS4L0lPlKkoMQ8,1020
|
|
@@ -210,9 +211,9 @@ synapse_sdk/utils/storage/providers/gcp.py,sha256=i2BQCu1Kej1If9SuNr2_lEyTcr5M_n
|
|
|
210
211
|
synapse_sdk/utils/storage/providers/http.py,sha256=2DhIulND47JOnS5ZY7MZUex7Su3peAPksGo1Wwg07L4,5828
|
|
211
212
|
synapse_sdk/utils/storage/providers/s3.py,sha256=ZmqekAvIgcQBdRU-QVJYv1Rlp6VHfXwtbtjTSphua94,2573
|
|
212
213
|
synapse_sdk/utils/storage/providers/sftp.py,sha256=_8s9hf0JXIO21gvm-JVS00FbLsbtvly4c-ETLRax68A,1426
|
|
213
|
-
synapse_sdk-1.0.
|
|
214
|
-
synapse_sdk-1.0.
|
|
215
|
-
synapse_sdk-1.0.
|
|
216
|
-
synapse_sdk-1.0.
|
|
217
|
-
synapse_sdk-1.0.
|
|
218
|
-
synapse_sdk-1.0.
|
|
214
|
+
synapse_sdk-1.0.0b5.dist-info/licenses/LICENSE,sha256=bKzmC5YAg4V1Fhl8OO_tqY8j62hgdncAkN7VrdjmrGk,1101
|
|
215
|
+
synapse_sdk-1.0.0b5.dist-info/METADATA,sha256=dE3YqjlFZ96vvRoQ59Htr65rSrvmibKDAvsGwOIB6U4,3718
|
|
216
|
+
synapse_sdk-1.0.0b5.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
217
|
+
synapse_sdk-1.0.0b5.dist-info/entry_points.txt,sha256=VNptJoGoNJI8yLXfBmhgUefMsmGI0m3-0YoMvrOgbxo,48
|
|
218
|
+
synapse_sdk-1.0.0b5.dist-info/top_level.txt,sha256=ytgJMRK1slVOKUpgcw3LEyHHP7S34J6n_gJzdkcSsw8,12
|
|
219
|
+
synapse_sdk-1.0.0b5.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|