synapse-sdk 1.0.0b2__py3-none-any.whl → 1.0.0b4__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.

Potentially problematic release.


This version of synapse-sdk might be problematic. Click here for more details.

@@ -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'),
@@ -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
- @click.command()
12
- @click.option('--agent', help='Agent name or ID')
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
- # Connect to agent and get code-server info
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
- click.echo(f'Agent info: {agent_info}')
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
- # Get code-server information
93
- try:
94
- info = client.get_code_server_info()
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
- # Display connection information
113
- click.echo('\n✅ Code-Server is available!')
107
+ def detect_and_encrypt_plugin(workspace_path: str) -> Optional[dict]:
108
+ """Detect and encrypt plugin code in the workspace.
114
109
 
115
- workspace = info.get('workspace', '/home/coder/workspace')
110
+ Args:
111
+ workspace_path: Path to check for plugin
116
112
 
117
- # Show web browser access
118
- click.echo('\n🌐 Web-based VS Code:')
119
- click.echo(f' URL: {info["url"]}')
120
- password = info.get('password')
121
- if password:
122
- click.echo(f' Password: {password}')
123
- else:
124
- click.echo(' Password: Not required (passwordless mode)')
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
- click.echo(f'\n📁 Workspace: {workspace}')
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
- # Optionally open in browser
129
- if open_browser and info.get('url'):
130
- import subprocess
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
- click.echo('\nAttempting to open in browser...')
178
+ # Default port if config not found or invalid
179
+ return 8880
133
180
 
134
- # Try to open browser, suppressing stderr to avoid xdg-open noise
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
- # Use subprocess to suppress xdg-open errors
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: {info["url"]}')
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: {info["url"]}')
233
+ click.echo(f'👉 Please manually open: {url_with_folder}')
145
234
  except Exception:
146
- # Fallback for other errors
147
- click.echo(f'👉 Please manually open: {info["url"]}')
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
- # Show additional instructions
150
- click.echo('\n📝 Quick Start:')
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
- import traceback
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
- click.echo(f'❌ Failed to connect to agent: {e}')
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
- # Show more detailed error in debug mode
161
- if '--debug' in click.get_current_context().params:
162
- click.echo('\nDebug trace:')
163
- click.echo(traceback.format_exc())
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
- click.echo('\nTroubleshooting:')
166
- click.echo('1. Check if agent is running and accessible')
167
- click.echo('2. Verify the agent has code-server support (may need agent update)')
168
- click.echo('3. Check agent logs for more details')
169
- click.echo('4. Try running with --no-open-browser to see connection details')
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
  """
@@ -291,6 +301,11 @@ class ExportAction(Action):
291
301
  'failed': 0,
292
302
  'success': 0,
293
303
  },
304
+ 'etc': {
305
+ 'stand_by': 0,
306
+ 'failed': 0,
307
+ 'success': 0,
308
+ },
294
309
  }
295
310
 
296
311
  def get_filtered_results(self, filters, handler):
@@ -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
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: synapse-sdk
3
- Version: 1.0.0b2
3
+ Version: 1.0.0b4
4
4
  Summary: synapse sdk
5
5
  Author-email: datamaker <developer@datamaker.io>
6
6
  License: MIT
@@ -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=tdOwJRf6qggHqFrXBTwvhUbkG0KZmJ_xDlDdDv7Wn8M,11663
10
- synapse_sdk/cli/code_server.py,sha256=vp5i0p8BO-bA3XrbqBtb95S0kGhQZwzmPJj42YTX72s,7042
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=x2jgORTjT7pJY67SLuc-5lMG6CD5OWpy8UgGeTf7IhA,270
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=UZaL6F7rHgbF5FSGpXuA3iDaSE1zJ193I_NhjfE1GWk,11598
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
@@ -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.0b2.dist-info/licenses/LICENSE,sha256=bKzmC5YAg4V1Fhl8OO_tqY8j62hgdncAkN7VrdjmrGk,1101
214
- synapse_sdk-1.0.0b2.dist-info/METADATA,sha256=yE09zDvM8Acnx0R_9du9F4lOMfrDajKDlGrY90k9ZQw,3718
215
- synapse_sdk-1.0.0b2.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
216
- synapse_sdk-1.0.0b2.dist-info/entry_points.txt,sha256=VNptJoGoNJI8yLXfBmhgUefMsmGI0m3-0YoMvrOgbxo,48
217
- synapse_sdk-1.0.0b2.dist-info/top_level.txt,sha256=ytgJMRK1slVOKUpgcw3LEyHHP7S34J6n_gJzdkcSsw8,12
218
- synapse_sdk-1.0.0b2.dist-info/RECORD,,
214
+ synapse_sdk-1.0.0b4.dist-info/licenses/LICENSE,sha256=bKzmC5YAg4V1Fhl8OO_tqY8j62hgdncAkN7VrdjmrGk,1101
215
+ synapse_sdk-1.0.0b4.dist-info/METADATA,sha256=5fqI0sn3Ro1CRZQVGAputa73_iZGd360Cxww_o6_K3Y,3718
216
+ synapse_sdk-1.0.0b4.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
217
+ synapse_sdk-1.0.0b4.dist-info/entry_points.txt,sha256=VNptJoGoNJI8yLXfBmhgUefMsmGI0m3-0YoMvrOgbxo,48
218
+ synapse_sdk-1.0.0b4.dist-info/top_level.txt,sha256=ytgJMRK1slVOKUpgcw3LEyHHP7S34J6n_gJzdkcSsw8,12
219
+ synapse_sdk-1.0.0b4.dist-info/RECORD,,