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

Files changed (85) hide show
  1. synapse_sdk/cli/__init__.py +246 -5
  2. synapse_sdk/cli/alias/utils.py +1 -1
  3. synapse_sdk/cli/config.py +339 -0
  4. synapse_sdk/cli/devtools.py +61 -0
  5. synapse_sdk/cli/plugin/publish.py +3 -4
  6. synapse_sdk/clients/agent/__init__.py +7 -2
  7. synapse_sdk/clients/agent/ray.py +37 -6
  8. synapse_sdk/clients/backend/__init__.py +5 -9
  9. synapse_sdk/clients/backend/annotation.py +4 -0
  10. synapse_sdk/clients/base.py +42 -3
  11. synapse_sdk/devtools/__init__.py +0 -0
  12. synapse_sdk/devtools/config.py +94 -0
  13. synapse_sdk/devtools/docs/.gitignore +20 -0
  14. synapse_sdk/devtools/docs/README.md +41 -0
  15. synapse_sdk/devtools/docs/blog/2019-05-28-first-blog-post.md +12 -0
  16. synapse_sdk/devtools/docs/blog/2019-05-29-long-blog-post.md +44 -0
  17. synapse_sdk/devtools/docs/blog/2021-08-01-mdx-blog-post.mdx +24 -0
  18. synapse_sdk/devtools/docs/blog/2021-08-26-welcome/docusaurus-plushie-banner.jpeg +0 -0
  19. synapse_sdk/devtools/docs/blog/2021-08-26-welcome/index.md +29 -0
  20. synapse_sdk/devtools/docs/blog/authors.yml +25 -0
  21. synapse_sdk/devtools/docs/blog/tags.yml +19 -0
  22. synapse_sdk/devtools/docs/docusaurus.config.ts +138 -0
  23. synapse_sdk/devtools/docs/package-lock.json +17455 -0
  24. synapse_sdk/devtools/docs/package.json +47 -0
  25. synapse_sdk/devtools/docs/sidebars.ts +36 -0
  26. synapse_sdk/devtools/docs/src/components/HomepageFeatures/index.tsx +71 -0
  27. synapse_sdk/devtools/docs/src/components/HomepageFeatures/styles.module.css +11 -0
  28. synapse_sdk/devtools/docs/src/css/custom.css +30 -0
  29. synapse_sdk/devtools/docs/src/pages/index.module.css +23 -0
  30. synapse_sdk/devtools/docs/src/pages/index.tsx +21 -0
  31. synapse_sdk/devtools/docs/src/pages/markdown-page.md +7 -0
  32. synapse_sdk/devtools/docs/static/.nojekyll +0 -0
  33. synapse_sdk/devtools/docs/static/img/docusaurus-social-card.jpg +0 -0
  34. synapse_sdk/devtools/docs/static/img/docusaurus.png +0 -0
  35. synapse_sdk/devtools/docs/static/img/favicon.ico +0 -0
  36. synapse_sdk/devtools/docs/static/img/logo.png +0 -0
  37. synapse_sdk/devtools/docs/static/img/undraw_docusaurus_mountain.svg +171 -0
  38. synapse_sdk/devtools/docs/static/img/undraw_docusaurus_react.svg +170 -0
  39. synapse_sdk/devtools/docs/static/img/undraw_docusaurus_tree.svg +40 -0
  40. synapse_sdk/devtools/docs/tsconfig.json +8 -0
  41. synapse_sdk/devtools/models.py +55 -0
  42. synapse_sdk/devtools/server.py +829 -0
  43. synapse_sdk/devtools/web/.gitignore +2 -0
  44. synapse_sdk/devtools/web/README.md +34 -0
  45. synapse_sdk/devtools/web/dist/index.html +17 -0
  46. synapse_sdk/devtools/web/index.html +16 -0
  47. synapse_sdk/devtools/web/jsconfig.json +15 -0
  48. synapse_sdk/devtools/web/package-lock.json +2609 -0
  49. synapse_sdk/devtools/web/package.json +27 -0
  50. synapse_sdk/devtools/web/pnpm-lock.yaml +1055 -0
  51. synapse_sdk/devtools/web/src/App.jsx +14 -0
  52. synapse_sdk/devtools/web/src/App.module.css +33 -0
  53. synapse_sdk/devtools/web/src/assets/favicon.ico +0 -0
  54. synapse_sdk/devtools/web/src/components/Breadcrumbs.jsx +42 -0
  55. synapse_sdk/devtools/web/src/components/Layout.jsx +12 -0
  56. synapse_sdk/devtools/web/src/components/LogViewer.jsx +266 -0
  57. synapse_sdk/devtools/web/src/components/MessageViewer.jsx +150 -0
  58. synapse_sdk/devtools/web/src/components/NavigationSidebar.jsx +137 -0
  59. synapse_sdk/devtools/web/src/components/ServerStatusBar.jsx +245 -0
  60. synapse_sdk/devtools/web/src/components/icons.jsx +325 -0
  61. synapse_sdk/devtools/web/src/index.css +470 -0
  62. synapse_sdk/devtools/web/src/index.jsx +15 -0
  63. synapse_sdk/devtools/web/src/logo.svg +1 -0
  64. synapse_sdk/devtools/web/src/router.jsx +34 -0
  65. synapse_sdk/devtools/web/src/utils/api.js +425 -0
  66. synapse_sdk/devtools/web/src/views/ApplicationDetailView.jsx +241 -0
  67. synapse_sdk/devtools/web/src/views/ApplicationsView.jsx +224 -0
  68. synapse_sdk/devtools/web/src/views/HomeView.jsx +197 -0
  69. synapse_sdk/devtools/web/src/views/JobDetailView.jsx +310 -0
  70. synapse_sdk/devtools/web/src/views/PluginView.jsx +914 -0
  71. synapse_sdk/devtools/web/vite.config.js +13 -0
  72. synapse_sdk/plugins/categories/neural_net/actions/tune.py +1 -1
  73. synapse_sdk/plugins/categories/pre_annotation/actions/to_task.py +236 -64
  74. synapse_sdk/plugins/categories/pre_annotation/templates/config.yaml +14 -2
  75. synapse_sdk/plugins/templates/plugin-config-schema.json +409 -0
  76. synapse_sdk/plugins/templates/schema.json +484 -0
  77. synapse_sdk/utils/converters/__init__.py +145 -0
  78. synapse_sdk/utils/converters/coco/__init__.py +0 -0
  79. synapse_sdk/utils/converters/coco/from_dm.py +269 -0
  80. {synapse_sdk-1.0.0a59.dist-info → synapse_sdk-1.0.0a60.dist-info}/METADATA +9 -22
  81. {synapse_sdk-1.0.0a59.dist-info → synapse_sdk-1.0.0a60.dist-info}/RECORD +85 -17
  82. {synapse_sdk-1.0.0a59.dist-info → synapse_sdk-1.0.0a60.dist-info}/WHEEL +0 -0
  83. {synapse_sdk-1.0.0a59.dist-info → synapse_sdk-1.0.0a60.dist-info}/entry_points.txt +0 -0
  84. {synapse_sdk-1.0.0a59.dist-info → synapse_sdk-1.0.0a60.dist-info}/licenses/LICENSE +0 -0
  85. {synapse_sdk-1.0.0a59.dist-info → synapse_sdk-1.0.0a60.dist-info}/top_level.txt +0 -0
@@ -1,13 +1,254 @@
1
+ import os
2
+
1
3
  import click
4
+ import inquirer
5
+ import requests
2
6
 
3
- from .alias import alias
7
+ from .config import config
8
+ from .devtools import devtools
4
9
  from .plugin import plugin
5
10
 
6
11
 
7
- @click.group()
8
- def cli():
9
- pass
12
+ def clear_screen():
13
+ """Clear the terminal screen"""
14
+ os.system('cls' if os.name == 'nt' else 'clear')
15
+
16
+
17
+ def check_backend_status():
18
+ """Check backend connection status and token validity"""
19
+ from synapse_sdk.devtools.config import get_backend_config
20
+
21
+ backend_config = get_backend_config()
22
+ if not backend_config:
23
+ return 'not_configured', 'No backend configured'
24
+
25
+ try:
26
+ # Try an authenticated endpoint to verify token validity
27
+ # Use /users/me/ which requires authentication
28
+ response = requests.get(
29
+ f'{backend_config["host"]}/users/me/',
30
+ headers={'Synapse-Access-Token': f'Token {backend_config["token"]}'},
31
+ timeout=5,
32
+ )
33
+
34
+ if response.status_code == 200:
35
+ return 'healthy', f'Connected to {backend_config["host"]}'
36
+ elif response.status_code == 401:
37
+ return 'auth_error', 'Invalid token (401)'
38
+ elif response.status_code == 403:
39
+ return 'forbidden', 'Access forbidden (403)'
40
+ elif response.status_code == 404:
41
+ # If /users/me/ doesn't exist, try /health as fallback
42
+ try:
43
+ health_response = requests.get(
44
+ f'{backend_config["host"]}/health',
45
+ headers={'Synapse-Access-Token': f'Token {backend_config["token"]}'},
46
+ timeout=3,
47
+ )
48
+ if health_response.status_code == 200:
49
+ return 'healthy', f'Connected to {backend_config["host"]}'
50
+ elif health_response.status_code == 401:
51
+ return 'auth_error', 'Invalid token (401)'
52
+ elif health_response.status_code == 403:
53
+ return 'forbidden', 'Access forbidden (403)'
54
+ else:
55
+ return 'error', f'HTTP {health_response.status_code}'
56
+ except: # noqa: E722
57
+ return 'error', 'Endpoint not found (404)'
58
+ else:
59
+ return 'error', f'HTTP {response.status_code}'
60
+
61
+ except requests.exceptions.Timeout:
62
+ return 'timeout', 'Connection timeout (>5s)'
63
+ except requests.exceptions.ConnectionError:
64
+ return 'connection_error', 'Connection failed'
65
+ except Exception as e:
66
+ return 'error', f'Connection error: {str(e)}'
67
+
68
+
69
+ def check_agent_status():
70
+ """Check agent configuration status"""
71
+ from synapse_sdk.devtools.config import load_devtools_config
72
+
73
+ config = load_devtools_config()
74
+ agent_config = config.get('agent', {})
75
+
76
+ if not agent_config.get('id'):
77
+ return 'not_configured', 'No agent selected'
78
+
79
+ return 'configured', f'{agent_config.get("name", "")} (ID: {agent_config["id"]})'
80
+
81
+
82
+ def display_connection_status():
83
+ """Display connection status for backend and agent"""
84
+ click.echo(click.style('Connection Status:', fg='white', bold=True))
85
+
86
+ # Check backend status (async with timeout)
87
+ backend_status, backend_msg = check_backend_status()
88
+
89
+ # Backend status with specific handling for auth errors
90
+ if backend_status == 'healthy':
91
+ click.echo(f'🟢 Backend: {click.style(backend_msg, fg="green")}')
92
+ elif backend_status == 'not_configured':
93
+ click.echo(f'🔴 Backend: {click.style(backend_msg, fg="yellow")}')
94
+ elif backend_status in ['auth_error', 'forbidden']:
95
+ click.echo(f'🔴 Backend: {click.style(backend_msg, fg="red", bold=True)}')
96
+ else:
97
+ click.echo(f'🔴 Backend: {click.style(backend_msg, fg="red")}')
98
+
99
+ # Agent status (config check only, no network call)
100
+ agent_status, agent_msg = check_agent_status()
101
+ if agent_status == 'configured':
102
+ click.echo(f'🟢 Agent: {click.style(agent_msg, fg="green")}')
103
+ else:
104
+ click.echo(f'🔴 Agent: {click.style(agent_msg, fg="yellow")}')
105
+
106
+ click.echo() # Empty line for spacing
107
+
108
+
109
+ def run_devtools(build=False):
110
+ """Run devtools with default settings"""
111
+ try:
112
+ from synapse_sdk.devtools.server import create_devtools_server
113
+
114
+ if build:
115
+ click.echo('Building frontend assets...')
116
+ build_frontend()
117
+
118
+ click.echo('Starting Synapse Devtools...')
119
+ server = create_devtools_server(host='127.0.0.1', port=8080)
120
+ server.start_server(open_browser=True)
121
+ except ImportError:
122
+ click.echo(
123
+ click.style(
124
+ 'Devtools dependencies not installed. Install with: pip install synapse-sdk[dashboard]', fg='red'
125
+ ),
126
+ err=True,
127
+ )
128
+ except KeyboardInterrupt:
129
+ click.echo('\nDevtools stopped.')
130
+ except Exception as e:
131
+ click.echo(click.style(f'Failed to start devtools: {e}', fg='red'), err=True)
132
+
133
+
134
+ def build_frontend():
135
+ """Build the frontend assets"""
136
+ import subprocess
137
+ from pathlib import Path
138
+
139
+ # Find the web directory
140
+ devtools_dir = Path(__file__).parent.parent / 'devtools' / 'web'
141
+
142
+ if not devtools_dir.exists():
143
+ click.echo(click.style(f'Frontend directory not found: {devtools_dir}', fg='red'), err=True)
144
+ return False
145
+
146
+ try:
147
+ # Check if npm is available
148
+ subprocess.run(['npm', '--version'], capture_output=True, check=True)
149
+ except (subprocess.CalledProcessError, FileNotFoundError):
150
+ click.echo(click.style('npm not found. Please install Node.js and npm.', fg='red'), err=True)
151
+ return False
152
+
153
+ try:
154
+ # Install dependencies if node_modules doesn't exist
155
+ if not (devtools_dir / 'node_modules').exists():
156
+ click.echo('Installing frontend dependencies...')
157
+ result = subprocess.run(['npm', 'install'], cwd=devtools_dir, capture_output=True, text=True)
158
+ if result.returncode != 0:
159
+ click.echo(click.style(f'npm install failed:\n{result.stderr}', fg='red'), err=True)
160
+ return False
161
+
162
+ # Build the frontend
163
+ click.echo('Building frontend...')
164
+ result = subprocess.run(['npm', 'run', 'build'], cwd=devtools_dir, capture_output=True, text=True)
165
+
166
+ if result.returncode != 0:
167
+ click.echo(click.style(f'Frontend build failed:\n{result.stderr}', fg='red'), err=True)
168
+ return False
169
+
170
+ click.echo(click.style('Frontend build completed successfully!', fg='green'))
171
+ return True
172
+
173
+ except Exception as e:
174
+ click.echo(click.style(f'Build failed: {e}', fg='red'), err=True)
175
+ return False
176
+
177
+
178
+ def run_config():
179
+ """Run the configuration menu"""
180
+ from .config import interactive_config
181
+
182
+ interactive_config()
183
+
184
+
185
+ @click.group(invoke_without_command=True)
186
+ @click.option('--dev-tools', is_flag=True, help='Start devtools immediately')
187
+ @click.option('--build', is_flag=True, help='Build frontend before starting devtools (only with --dev-tools)')
188
+ @click.pass_context
189
+ def cli(ctx, dev_tools, build):
190
+ """Synapse SDK - Interactive CLI"""
191
+
192
+ # Handle --dev-tools flag
193
+ if dev_tools:
194
+ if build:
195
+ run_devtools(build=True)
196
+ else:
197
+ run_devtools(build=False)
198
+ return
199
+
200
+ # Handle --build flag without --dev-tools (show warning)
201
+ if build and not dev_tools:
202
+ click.echo(click.style('Warning: --build flag requires --dev-tools flag', fg='yellow'))
203
+ click.echo('Use: synapse --dev-tools --build')
204
+ return
205
+
206
+ if ctx.invoked_subcommand is None:
207
+ while True:
208
+ clear_screen() # Always clear screen at start of main menu loop
209
+ click.echo(click.style('🚀 Synapse SDK', fg='cyan', bold=True))
210
+ click.echo()
211
+ display_connection_status()
212
+
213
+ try:
214
+ questions = [
215
+ inquirer.List(
216
+ 'choice',
217
+ message='Select an option:',
218
+ choices=[
219
+ ('🌐 Run Dev Tools', 'devtools'),
220
+ ('⚙️ Configuration', 'config'),
221
+ ('🔌 Plugin Management', 'plugin'),
222
+ ('🚪 Exit', 'exit'),
223
+ ],
224
+ )
225
+ ]
226
+
227
+ answers = inquirer.prompt(questions)
228
+ if not answers or answers['choice'] == 'exit':
229
+ clear_screen()
230
+ click.echo(click.style('👋 Goodbye!', fg='green'))
231
+ break
232
+
233
+ if answers['choice'] == 'devtools':
234
+ run_devtools()
235
+ break # Exit after devtools finishes
236
+ elif answers['choice'] == 'config':
237
+ run_config()
238
+ # Config menu returned, continue to main menu loop
239
+ elif answers['choice'] == 'plugin':
240
+ click.echo(click.style('🔌 Plugin Management', fg='cyan', bold=True))
241
+ click.echo('For plugin management, use: synapse plugin --help\n')
242
+ click.echo('Press Enter to return to main menu...')
243
+ input()
244
+ # Don't break - continue to main menu loop
245
+
246
+ except (KeyboardInterrupt, EOFError):
247
+ clear_screen()
248
+ click.echo(click.style('👋 Goodbye!', fg='yellow'))
249
+ break
10
250
 
11
251
 
12
252
  cli.add_command(plugin)
13
- cli.add_command(alias)
253
+ cli.add_command(config)
254
+ cli.add_command(devtools)
@@ -2,7 +2,7 @@ from pathlib import Path
2
2
 
3
3
  from dotenv import dotenv_values, load_dotenv, set_key, unset_key
4
4
 
5
- CONFIG_DIR = Path.home() / '.config' / 'synapse_alias'
5
+ CONFIG_DIR = Path.home() / '.config' / 'synapse'
6
6
  DEFAULT_FILE = CONFIG_DIR / '__default__'
7
7
 
8
8
 
@@ -0,0 +1,339 @@
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 get_agent_config():
22
+ """Get current agent configuration"""
23
+ config = load_devtools_config()
24
+ return config.get('agent', {})
25
+
26
+
27
+ def get_tenant_config():
28
+ """Get current tenant configuration"""
29
+ config = load_devtools_config()
30
+ return config.get('tenant', {})
31
+
32
+
33
+ def set_tenant_config(tenant_code: str, tenant_name: str = None):
34
+ """Set tenant configuration"""
35
+ config = load_devtools_config()
36
+ config['tenant'] = {'code': tenant_code}
37
+ if tenant_name:
38
+ config['tenant']['name'] = tenant_name
39
+ save_devtools_config(config)
40
+
41
+
42
+ def clear_tenant_config():
43
+ """Clear tenant configuration"""
44
+ config = load_devtools_config()
45
+ if 'tenant' in config:
46
+ del config['tenant']
47
+ save_devtools_config(config)
48
+
49
+
50
+ def set_agent_config(agent_id: str, agent_name: str = None, agent_url: str = None, agent_token: str = None):
51
+ """Set agent configuration"""
52
+ config = load_devtools_config()
53
+ config['agent'] = {'id': agent_id}
54
+ if agent_name:
55
+ config['agent']['name'] = agent_name
56
+ if agent_url:
57
+ config['agent']['url'] = agent_url
58
+ if agent_token:
59
+ config['agent']['token'] = agent_token
60
+
61
+ save_devtools_config(config)
62
+
63
+
64
+ def clear_agent_config():
65
+ """Clear agent configuration"""
66
+ config = load_devtools_config()
67
+ if 'agent' in config:
68
+ del config['agent']
69
+ save_devtools_config(config)
70
+
71
+
72
+ def fetch_agents_from_backend():
73
+ """Fetch available agents from the backend"""
74
+ backend_config = get_backend_config()
75
+ if not backend_config:
76
+ return None, 'No backend configuration found. Configure backend first.'
77
+
78
+ def extract_uuid(string):
79
+ import re
80
+
81
+ """Extract UUID between 'agents/' and '/node_install_script' from a string."""
82
+ pattern = r'agents/([a-f0-9]{40})/node_install_script'
83
+ match = re.search(pattern, string)
84
+ return match.group(1) if match else None
85
+
86
+ try:
87
+ response = requests.get(
88
+ f'{backend_config["host"]}/agents/',
89
+ headers={'Synapse-Access-Token': f'Token {backend_config["token"]}'},
90
+ timeout=10,
91
+ )
92
+ if response.status_code == 200:
93
+ try:
94
+ data = response.json()
95
+ results = data.get('results', [])
96
+ for result in results:
97
+ _node_install_script = result.get('node_install_script')
98
+ if _node_install_script:
99
+ result['token'] = extract_uuid(_node_install_script)
100
+ return results, None
101
+ except ValueError:
102
+ return None, 'Invalid JSON response from server'
103
+ elif response.status_code == 401:
104
+ return None, 'Authentication failed. Check your backend token.'
105
+ elif response.status_code == 403:
106
+ return None, 'Access forbidden. Check your permissions.'
107
+ else:
108
+ return None, f'Failed to fetch agents: HTTP {response.status_code}'
109
+
110
+ except requests.exceptions.Timeout:
111
+ return None, 'Request timeout. Check your network connection.'
112
+ except requests.exceptions.ConnectionError:
113
+ return None, 'Connection failed. Check backend host URL.'
114
+ except Exception as e:
115
+ return None, f'Error fetching agents: {str(e)}'
116
+
117
+
118
+ def configure_backend():
119
+ """Interactive backend configuration"""
120
+ backend_config = get_backend_config()
121
+
122
+ if backend_config:
123
+ click.echo(f'Current backend: {backend_config["host"]}')
124
+ click.echo(f'Token: {backend_config["token"][:8]}...')
125
+ click.echo()
126
+
127
+ questions = [
128
+ inquirer.List(
129
+ 'action',
130
+ message='What would you like to do?',
131
+ choices=[
132
+ ('Configure new backend', 'configure'),
133
+ ('Show current configuration', 'show'),
134
+ ('Clear configuration', 'clear'),
135
+ ('← Back to main menu', 'back'),
136
+ ],
137
+ )
138
+ ]
139
+
140
+ answers = inquirer.prompt(questions)
141
+ if not answers or answers['action'] == 'back':
142
+ return
143
+
144
+ if answers['action'] == 'show':
145
+ if backend_config:
146
+ click.echo(click.style('\n✓ Backend Configuration:', fg='green'))
147
+ click.echo(f' Host: {backend_config["host"]}')
148
+ click.echo(f' Token: {backend_config["token"]}')
149
+ else:
150
+ click.echo(click.style('\n⚠ No backend configuration found.', fg='yellow'))
151
+ return
152
+
153
+ if answers['action'] == 'clear':
154
+ confirm = inquirer.confirm('Are you sure you want to clear the backend configuration?')
155
+ if confirm:
156
+ clear_backend_config()
157
+ click.echo(click.style('\n✓ Backend configuration cleared.', fg='yellow'))
158
+ return
159
+
160
+ if answers['action'] == 'configure':
161
+ config_questions = [
162
+ inquirer.Text(
163
+ 'host',
164
+ message='Backend host URL',
165
+ default=backend_config['host'] if backend_config else 'https://api.synapse.sh',
166
+ ),
167
+ inquirer.Password('token', message='API token'),
168
+ ]
169
+
170
+ config_answers = inquirer.prompt(config_questions)
171
+ if config_answers and config_answers['token']:
172
+ set_backend_config(config_answers['host'], config_answers['token'])
173
+ click.echo(click.style('\n✓ Backend configuration saved!', fg='green'))
174
+ click.echo(f'Host: {config_answers["host"]}')
175
+ click.echo(f'Token: {config_answers["token"][:8]}...')
176
+
177
+
178
+ def configure_agent():
179
+ """Interactive agent configuration"""
180
+ agent_config = get_agent_config()
181
+
182
+ if agent_config:
183
+ click.echo(f'Current agent: {agent_config.get("name", "Unknown")} ({agent_config.get("id")})')
184
+ click.echo()
185
+
186
+ questions = [
187
+ inquirer.List(
188
+ 'action',
189
+ message='What would you like to do?',
190
+ choices=[
191
+ ('Select agent', 'select'),
192
+ ('Set agent ID manually', 'manual'),
193
+ ('← Back to main menu', 'back'),
194
+ ],
195
+ )
196
+ ]
197
+
198
+ answers = inquirer.prompt(questions)
199
+ if not answers or answers['action'] == 'back':
200
+ return
201
+
202
+ if answers['action'] == 'select':
203
+ click.echo('Fetching available agents...')
204
+ agents, error = fetch_agents_from_backend()
205
+
206
+ if error:
207
+ click.echo(click.style(f'\nError: {error}', fg='red'))
208
+ return
209
+
210
+ if not agents:
211
+ click.echo(click.style('\n⚠ No agents found in current workspace.', fg='yellow'))
212
+ return
213
+
214
+ # Create choices for agent selection
215
+ agent_choices = []
216
+ for agent in agents:
217
+ status_indicator = '🟢' if agent.get('status', '').lower() == 'connected' else '🔴'
218
+ display_name = f'{status_indicator} {agent["name"]} ({agent["id"]})'
219
+ if agent.get('url'):
220
+ display_name += f' - {agent["url"]}'
221
+ agent_choices.append((display_name, agent))
222
+
223
+ agent_choices.append(('← Cancel', None))
224
+
225
+ agent_questions = [inquirer.List('selected_agent', message='Select an agent:', choices=agent_choices)]
226
+
227
+ agent_answers = inquirer.prompt(agent_questions)
228
+ if agent_answers and agent_answers['selected_agent']:
229
+ selected = agent_answers['selected_agent']
230
+ set_agent_config(selected['id'], selected.get('name'), selected.get('url'), selected.get('token'))
231
+ click.echo(click.style('\n✓ Agent configured!', fg='green'))
232
+ click.echo(f'Selected: {selected["name"]} ({selected["id"]})')
233
+ if selected.get('url'):
234
+ click.echo(f'URL: {selected["url"]}')
235
+
236
+ if answers['action'] == 'manual':
237
+ manual_questions = [
238
+ inquirer.Text('agent_id', message='Agent ID', default=agent_config.get('id', '') if agent_config else ''),
239
+ inquirer.Text(
240
+ 'agent_url', message='Agent URL', default=agent_config.get('url', '') if agent_config else ''
241
+ ),
242
+ inquirer.Text(
243
+ 'agent_token', message='Agent Token', default=agent_config.get('token', '') if agent_config else ''
244
+ ),
245
+ ]
246
+
247
+ manual_answers = inquirer.prompt(manual_questions)
248
+ if manual_answers and manual_answers['agent_id']:
249
+ set_agent_config(
250
+ manual_answers['agent_id'], None, manual_answers.get('agent_url'), manual_answers.get('agent_token')
251
+ )
252
+ click.echo(click.style('\n✓ Agent configured!', fg='green'))
253
+ click.echo(f'Agent ID: {manual_answers["agent_id"]}')
254
+ if manual_answers.get('agent_url'):
255
+ click.echo(f'Agent URL: {manual_answers["agent_url"]}')
256
+ return
257
+
258
+
259
+ def show_current_config():
260
+ """Show all current configuration"""
261
+ backend_config = get_backend_config()
262
+ agent_config = get_agent_config()
263
+
264
+ click.echo(click.style('\n📋 Current Configuration', fg='cyan', bold=True))
265
+ click.echo('=' * 30)
266
+
267
+ # Backend section
268
+ click.echo(click.style('\n🔗 Backend:', fg='blue', bold=True))
269
+ if backend_config:
270
+ click.echo(f' Host: {backend_config["host"]}')
271
+ click.echo(f' Token: {backend_config["token"][:8]}...')
272
+ click.echo(click.style(' Status: ✓ Configured', fg='green'))
273
+ else:
274
+ click.echo(click.style(' Status: ✗ Not configured', fg='red'))
275
+
276
+ # Agent section
277
+ click.echo(click.style('\n🤖 Agent:', fg='blue', bold=True))
278
+ if agent_config:
279
+ click.echo(f' ID: {agent_config.get("id", "Not set")}')
280
+ if 'name' in agent_config:
281
+ click.echo(f' Name: {agent_config["name"]}')
282
+ click.echo(click.style(' Status: ✓ Configured', fg='green'))
283
+ else:
284
+ click.echo(click.style(' Status: ✗ Not configured', fg='red'))
285
+
286
+
287
+ def interactive_config():
288
+ while True:
289
+ clear_screen()
290
+ click.echo(click.style('🔧 Configuration', fg='cyan', bold=True))
291
+ click.echo('Configure your Synapse settings\n')
292
+
293
+ questions = [
294
+ inquirer.List(
295
+ 'choice',
296
+ message='What would you like to configure?',
297
+ choices=[
298
+ ('Synapse Backend Host', 'backend'),
299
+ ('Synapse Agent', 'agent'),
300
+ ('Show Current Config', 'show'),
301
+ ('← Back to Main Menu', 'exit'),
302
+ ],
303
+ )
304
+ ]
305
+
306
+ try:
307
+ answers = inquirer.prompt(questions)
308
+ if not answers or answers['choice'] == 'exit':
309
+ clear_screen() # Clear screen when exiting config
310
+ break
311
+
312
+ clear_screen()
313
+
314
+ if answers['choice'] == 'backend':
315
+ configure_backend()
316
+ click.echo('\nPress Enter to continue...')
317
+ input()
318
+ elif answers['choice'] == 'agent':
319
+ configure_agent()
320
+ click.echo('\nPress Enter to continue...')
321
+ input()
322
+ elif answers['choice'] == 'show':
323
+ show_current_config()
324
+ click.echo('\nPress Enter to continue...')
325
+ input()
326
+
327
+ except (KeyboardInterrupt, EOFError):
328
+ clear_screen() # Clear screen on interrupt
329
+ break
330
+ except Exception as e:
331
+ click.echo(click.style(f'\nError: {str(e)}', fg='red'))
332
+ click.echo('\nPress Enter to continue...')
333
+ input()
334
+
335
+
336
+ @click.command()
337
+ def config():
338
+ """Configure Synapse settings interactively"""
339
+ interactive_config()
@@ -0,0 +1,61 @@
1
+ import os
2
+ import sys
3
+
4
+ import click
5
+
6
+ from synapse_sdk.i18n import gettext as _
7
+
8
+
9
+ @click.command()
10
+ @click.option('--host', default=None, help='Host to bind the devtools server')
11
+ @click.option('--port', default=None, type=int, help='Port to bind the devtools server')
12
+ @click.option('--no-browser', is_flag=True, help='Do not open browser automatically')
13
+ @click.option('--debug', is_flag=True, help='Run in debug mode')
14
+ @click.option('--build', is_flag=True, help='Build frontend assets before starting server')
15
+ def devtools(host, port, no_browser, debug, build):
16
+ """Start the Synapse devtools web interface"""
17
+
18
+ try:
19
+ from synapse_sdk.devtools.config import get_server_config
20
+ from synapse_sdk.devtools.server import create_devtools_server
21
+ except ImportError:
22
+ click.echo(
23
+ click.style(
24
+ _('Devtools dependencies not installed. Install with: pip install synapse-sdk[devtools]'), fg='red'
25
+ ),
26
+ err=True,
27
+ )
28
+ sys.exit(1)
29
+
30
+ if build:
31
+ click.echo('Building frontend assets...')
32
+ from synapse_sdk.cli import build_frontend
33
+
34
+ if not build_frontend():
35
+ click.echo(click.style('Build failed, continuing with existing assets...', fg='yellow'))
36
+
37
+ if debug:
38
+ click.echo(_('Starting devtools in debug mode...'))
39
+ os.environ['UVICORN_LOG_LEVEL'] = 'debug'
40
+
41
+ click.echo('Starting Synapse Devtools...')
42
+
43
+ # Get server configuration from config file
44
+ server_config = get_server_config()
45
+
46
+ # Use CLI arguments if provided, otherwise use config defaults
47
+ final_host = host if host is not None else server_config['host']
48
+ final_port = port if port is not None else server_config['port']
49
+
50
+ # Create and start the devtools server
51
+ # Pass the current working directory as the plugin directory
52
+ plugin_directory = os.getcwd()
53
+ server = create_devtools_server(host=final_host, port=final_port, plugin_directory=plugin_directory)
54
+
55
+ try:
56
+ server.start_server(open_browser=not no_browser)
57
+ except KeyboardInterrupt:
58
+ click.echo(_('\nDevtools stopped.'))
59
+ except Exception as e:
60
+ click.echo(click.style(f'Failed to start devtools: {e}', fg='red'), err=True)
61
+ sys.exit(1)
@@ -10,11 +10,10 @@ from synapse_sdk.plugins.upload import archive
10
10
 
11
11
  @click.command()
12
12
  @click.option('--host', required=True)
13
- @click.option('--user_token', required=True)
14
- @click.option('--tenant', required=True)
13
+ @click.option('--access_token', required=True)
15
14
  @click.option('--debug_modules', default='', envvar='SYNAPSE_DEBUG_MODULES')
16
15
  @click.pass_context
17
- def publish(ctx, host, user_token, tenant, debug_modules):
16
+ def publish(ctx, host, access_token, debug_modules):
18
17
  debug = ctx.obj['DEBUG']
19
18
 
20
19
  plugin_release = PluginRelease()
@@ -28,7 +27,7 @@ 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
32
  click.secho(
34
33
  _('Successfully published "{}" ({}) to synapse backend!').format(plugin_release.name, plugin_release.code),