synapse-sdk 1.0.0a98__py3-none-any.whl → 1.0.0b2__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 (65) hide show
  1. synapse_sdk/cli/__init__.py +139 -84
  2. synapse_sdk/cli/code_server.py +169 -0
  3. synapse_sdk/cli/config.py +105 -4
  4. synapse_sdk/cli/devtools.py +54 -34
  5. synapse_sdk/clients/base.py +3 -4
  6. synapse_sdk/devtools/server.py +24 -791
  7. synapse_sdk/devtools/streamlit_app/__init__.py +5 -0
  8. synapse_sdk/devtools/streamlit_app/app.py +128 -0
  9. synapse_sdk/devtools/streamlit_app/services/__init__.py +11 -0
  10. synapse_sdk/devtools/streamlit_app/services/job_service.py +233 -0
  11. synapse_sdk/devtools/streamlit_app/services/plugin_service.py +236 -0
  12. synapse_sdk/devtools/streamlit_app/services/serve_service.py +95 -0
  13. synapse_sdk/devtools/streamlit_app/ui/__init__.py +15 -0
  14. synapse_sdk/devtools/streamlit_app/ui/config_tab.py +76 -0
  15. synapse_sdk/devtools/streamlit_app/ui/deployment_tab.py +66 -0
  16. synapse_sdk/devtools/streamlit_app/ui/http_tab.py +125 -0
  17. synapse_sdk/devtools/streamlit_app/ui/jobs_tab.py +573 -0
  18. synapse_sdk/devtools/streamlit_app/ui/serve_tab.py +346 -0
  19. synapse_sdk/devtools/streamlit_app/ui/status_bar.py +118 -0
  20. synapse_sdk/devtools/streamlit_app/utils/__init__.py +40 -0
  21. synapse_sdk/devtools/streamlit_app/utils/json_viewer.py +197 -0
  22. synapse_sdk/devtools/streamlit_app/utils/log_formatter.py +38 -0
  23. synapse_sdk/devtools/streamlit_app/utils/styles.py +241 -0
  24. synapse_sdk/devtools/streamlit_app/utils/ui_components.py +289 -0
  25. synapse_sdk/devtools/streamlit_app.py +10 -0
  26. synapse_sdk/plugins/categories/upload/actions/upload.py +2 -1
  27. synapse_sdk/utils/converters/coco/from_dm.py +2 -2
  28. synapse_sdk/utils/converters/dm/__init__.py +0 -1
  29. {synapse_sdk-1.0.0a98.dist-info → synapse_sdk-1.0.0b2.dist-info}/METADATA +4 -6
  30. {synapse_sdk-1.0.0a98.dist-info → synapse_sdk-1.0.0b2.dist-info}/RECORD +34 -45
  31. synapse_sdk/devtools/models.py +0 -55
  32. synapse_sdk/devtools/utils.py +0 -52
  33. synapse_sdk/devtools/web/.gitignore +0 -2
  34. synapse_sdk/devtools/web/README.md +0 -34
  35. synapse_sdk/devtools/web/dist/index.html +0 -17
  36. synapse_sdk/devtools/web/index.html +0 -16
  37. synapse_sdk/devtools/web/jsconfig.json +0 -15
  38. synapse_sdk/devtools/web/package-lock.json +0 -2609
  39. synapse_sdk/devtools/web/package.json +0 -27
  40. synapse_sdk/devtools/web/pnpm-lock.yaml +0 -1055
  41. synapse_sdk/devtools/web/src/App.jsx +0 -14
  42. synapse_sdk/devtools/web/src/App.module.css +0 -33
  43. synapse_sdk/devtools/web/src/assets/favicon.ico +0 -0
  44. synapse_sdk/devtools/web/src/components/Breadcrumbs.jsx +0 -42
  45. synapse_sdk/devtools/web/src/components/Layout.jsx +0 -12
  46. synapse_sdk/devtools/web/src/components/LogViewer.jsx +0 -280
  47. synapse_sdk/devtools/web/src/components/MessageViewer.jsx +0 -150
  48. synapse_sdk/devtools/web/src/components/NavigationSidebar.jsx +0 -128
  49. synapse_sdk/devtools/web/src/components/ServerStatusBar.jsx +0 -245
  50. synapse_sdk/devtools/web/src/components/icons.jsx +0 -325
  51. synapse_sdk/devtools/web/src/index.css +0 -470
  52. synapse_sdk/devtools/web/src/index.jsx +0 -15
  53. synapse_sdk/devtools/web/src/logo.svg +0 -1
  54. synapse_sdk/devtools/web/src/router.jsx +0 -34
  55. synapse_sdk/devtools/web/src/utils/api.js +0 -442
  56. synapse_sdk/devtools/web/src/views/ApplicationDetailView.jsx +0 -241
  57. synapse_sdk/devtools/web/src/views/ApplicationsView.jsx +0 -224
  58. synapse_sdk/devtools/web/src/views/HomeView.jsx +0 -197
  59. synapse_sdk/devtools/web/src/views/JobDetailView.jsx +0 -310
  60. synapse_sdk/devtools/web/src/views/PluginView.jsx +0 -914
  61. synapse_sdk/devtools/web/vite.config.js +0 -13
  62. {synapse_sdk-1.0.0a98.dist-info → synapse_sdk-1.0.0b2.dist-info}/WHEEL +0 -0
  63. {synapse_sdk-1.0.0a98.dist-info → synapse_sdk-1.0.0b2.dist-info}/entry_points.txt +0 -0
  64. {synapse_sdk-1.0.0a98.dist-info → synapse_sdk-1.0.0b2.dist-info}/licenses/LICENSE +0 -0
  65. {synapse_sdk-1.0.0a98.dist-info → synapse_sdk-1.0.0b2.dist-info}/top_level.txt +0 -0
@@ -4,6 +4,7 @@ import click
4
4
  import inquirer
5
5
  import requests
6
6
 
7
+ from .code_server import code_server
7
8
  from .config import config
8
9
  from .devtools import devtools
9
10
  from .plugin import plugin
@@ -108,88 +109,15 @@ def display_connection_status():
108
109
 
109
110
  def run_devtools(build=True):
110
111
  """Run devtools with default settings"""
111
- try:
112
- from synapse_sdk.devtools.config import get_server_config
113
- from synapse_sdk.devtools.server import create_devtools_server
114
- from synapse_sdk.devtools.utils import find_available_port, is_port_available
115
-
116
- if build:
117
- click.echo('Building...')
118
- build_frontend()
119
-
120
- click.echo('Starting Synapse Devtools...')
121
-
122
- # Get server configuration defaults
123
- server_config = get_server_config()
124
- host = server_config['host']
125
- port = server_config['port']
126
-
127
- # Check if the port is available, fallback to next available port if not
128
- if not is_port_available(host, port):
129
- try:
130
- fallback_port = find_available_port(host, port + 1)
131
- click.echo(click.style(f'Port {port} is in use, falling back to port {fallback_port}', fg='yellow'))
132
- port = fallback_port
133
- except RuntimeError as e:
134
- click.echo(click.style(f'Failed to find available port: {e}', fg='red'), err=True)
135
- return
136
-
137
- server = create_devtools_server(host=host, port=port)
138
- server.start_server()
139
- except ImportError:
140
- click.echo(
141
- click.style(
142
- 'Devtools dependencies not installed. Install with: pip install synapse-sdk[dashboard]', fg='red'
143
- ),
144
- err=True,
145
- )
146
- except KeyboardInterrupt:
147
- click.echo('\nDevtools stopped.')
148
- except Exception as e:
149
- click.echo(click.style(f'Failed to start devtools: {e}', fg='red'), err=True)
150
-
151
-
152
- def build_frontend():
153
- """Build the frontend assets"""
154
- import subprocess
155
- from pathlib import Path
156
-
157
- # Find the web directory
158
- devtools_dir = Path(__file__).parent.parent / 'devtools' / 'web'
159
-
160
- if not devtools_dir.exists():
161
- click.echo(click.style(f'Frontend directory not found: {devtools_dir}', fg='red'), err=True)
162
- return False
163
-
164
- try:
165
- # Check if npm is available
166
- subprocess.run(['npm', '--version'], capture_output=True, check=True)
167
- except (subprocess.CalledProcessError, FileNotFoundError):
168
- click.echo(click.style('npm not found. Please install Node.js and npm.', fg='red'), err=True)
169
- return False
170
-
171
- try:
172
- # Install dependencies if node_modules doesn't exist
173
- if not (devtools_dir / 'node_modules').exists():
174
- click.echo('Installing dependencies...')
175
- result = subprocess.run(['npm', 'install'], cwd=devtools_dir, capture_output=True, text=True)
176
- if result.returncode != 0:
177
- click.echo(click.style(f'npm install failed:\n{result.stderr}', fg='red'), err=True)
178
- return False
112
+ # Import the devtools command and run it
113
+ from .devtools import devtools
179
114
 
180
- # Build the frontend
181
- result = subprocess.run(['npm', 'run', 'build'], cwd=devtools_dir, capture_output=True, text=True)
115
+ # Use default settings
116
+ ctx = click.Context(devtools)
117
+ ctx.invoke(devtools, host=None, port=None, debug=False)
182
118
 
183
- if result.returncode != 0:
184
- click.echo(click.style(f'Frontend build failed:\n{result.stderr}', fg='red'), err=True)
185
- return False
186
119
 
187
- click.echo(click.style('Build completed successfully!', fg='green'))
188
- return True
189
-
190
- except Exception as e:
191
- click.echo(click.style(f'Build failed: {e}', fg='red'), err=True)
192
- return False
120
+ # build_frontend function removed - no longer needed with Streamlit
193
121
 
194
122
 
195
123
  def run_config():
@@ -199,6 +127,128 @@ def run_config():
199
127
  interactive_config()
200
128
 
201
129
 
130
+ def run_plugin_management():
131
+ """Run interactive plugin management"""
132
+ while True:
133
+ clear_screen()
134
+ click.echo(click.style('🔌 Plugin Management', fg='cyan', bold=True))
135
+ click.echo('Manage your Synapse plugins\n')
136
+
137
+ questions = [
138
+ inquirer.List(
139
+ 'action',
140
+ message='What would you like to do?',
141
+ choices=[
142
+ ('Create new plugin', 'create'),
143
+ ('Run plugin locally', 'run'),
144
+ ('Publish plugin', 'publish'),
145
+ ('← Back to main menu', 'back'),
146
+ ],
147
+ )
148
+ ]
149
+
150
+ answers = inquirer.prompt(questions)
151
+ if not answers or answers['action'] == 'back':
152
+ clear_screen()
153
+ break
154
+
155
+ if answers['action'] == 'create':
156
+ click.echo('\nCreating new plugin...')
157
+ from .plugin.create import create
158
+
159
+ ctx = click.Context(create)
160
+ ctx.invoke(create)
161
+ click.echo('\nPress Enter to continue...')
162
+ input()
163
+
164
+ elif answers['action'] == 'run':
165
+ # Get plugin action and parameters
166
+ run_questions = [
167
+ inquirer.Text('action', message='Plugin action to run'),
168
+ inquirer.Text('params', message='Parameters (JSON format)', default='{}'),
169
+ inquirer.List(
170
+ 'run_by',
171
+ message='Run by',
172
+ choices=[
173
+ ('Script (local)', 'script'),
174
+ ('Agent', 'agent'),
175
+ ('Backend', 'backend'),
176
+ ],
177
+ default='script',
178
+ ),
179
+ ]
180
+
181
+ run_answers = inquirer.prompt(run_questions)
182
+ if run_answers:
183
+ click.echo('\nRunning plugin...')
184
+ from .plugin.run import run
185
+
186
+ ctx = click.Context(run)
187
+ ctx.obj = {'DEBUG': False}
188
+
189
+ try:
190
+ if run_answers['run_by'] == 'script':
191
+ ctx.invoke(
192
+ run,
193
+ action=run_answers['action'],
194
+ params=run_answers['params'],
195
+ job_id=None,
196
+ direct=False,
197
+ run_by='script',
198
+ agent_host=None,
199
+ agent_token=None,
200
+ host=None,
201
+ agent=None,
202
+ user_token=None,
203
+ tenant=None,
204
+ )
205
+ else:
206
+ click.echo(click.style('\nNote: For agent/backend runs, use the command line:', fg='yellow'))
207
+ cmd = (
208
+ f"synapse plugin run {run_answers['action']} '{run_answers['params']}' "
209
+ f'--run-by {run_answers["run_by"]}'
210
+ )
211
+ click.echo(cmd)
212
+ except Exception as e:
213
+ click.echo(click.style(f'\nError: {str(e)}', fg='red'))
214
+
215
+ click.echo('\nPress Enter to continue...')
216
+ input()
217
+
218
+ elif answers['action'] == 'publish':
219
+ # Get backend configuration
220
+ from synapse_sdk.devtools.config import get_backend_config
221
+
222
+ backend_config = get_backend_config()
223
+
224
+ if not backend_config:
225
+ click.echo(click.style('\n⚠ No backend configured. Please configure backend first.', fg='yellow'))
226
+ click.echo('\nPress Enter to continue...')
227
+ input()
228
+ continue
229
+
230
+ publish_questions = [
231
+ inquirer.Confirm('debug', message='Publish in debug mode?', default=True),
232
+ inquirer.Confirm('confirm', message=f'Publish plugin to {backend_config["host"]}?', default=True),
233
+ ]
234
+
235
+ publish_answers = inquirer.prompt(publish_questions)
236
+ if publish_answers and publish_answers['confirm']:
237
+ click.echo('\nPublishing plugin...')
238
+ from .plugin.publish import _publish
239
+
240
+ debug_mode = publish_answers.get('debug', True)
241
+ try:
242
+ _publish(backend_config['host'], backend_config['token'], debug=debug_mode)
243
+ except Exception as e:
244
+ click.echo(click.style(f'\nError: {str(e)}', fg='red'))
245
+ click.echo('\nPress Enter to continue...')
246
+ input()
247
+ else:
248
+ click.echo('\nPress Enter to continue...')
249
+ input()
250
+
251
+
202
252
  @click.group(invoke_without_command=True)
203
253
  @click.option('--dev-tools', is_flag=True, help='Start devtools immediately')
204
254
  @click.pass_context
@@ -215,7 +265,6 @@ def cli(ctx, dev_tools):
215
265
  clear_screen() # Always clear screen at start of main menu loop
216
266
  click.echo(click.style('🚀 Synapse SDK', fg='cyan', bold=True))
217
267
  click.echo()
218
- display_connection_status()
219
268
 
220
269
  try:
221
270
  questions = [
@@ -224,6 +273,7 @@ def cli(ctx, dev_tools):
224
273
  message='Select an option:',
225
274
  choices=[
226
275
  ('🌐 Run Dev Tools', 'devtools'),
276
+ ('💻 Code-Server IDE', 'code_server'),
227
277
  ('⚙️ Configuration', 'config'),
228
278
  ('🔌 Plugin Management', 'plugin'),
229
279
  ('🚪 Exit', 'exit'),
@@ -240,14 +290,18 @@ def cli(ctx, dev_tools):
240
290
  if answers['choice'] == 'devtools':
241
291
  run_devtools()
242
292
  break # Exit after devtools finishes
293
+ elif answers['choice'] == 'code_server':
294
+ from .code_server import code_server
295
+
296
+ ctx.invoke(code_server)
297
+ click.echo('\nPress Enter to return to main menu...')
298
+ input()
299
+ # Continue to main menu loop
243
300
  elif answers['choice'] == 'config':
244
301
  run_config()
245
302
  # Config menu returned, continue to main menu loop
246
303
  elif answers['choice'] == 'plugin':
247
- click.echo(click.style('🔌 Plugin Management', fg='cyan', bold=True))
248
- click.echo('For plugin management, use: synapse plugin --help\n')
249
- click.echo('Press Enter to return to main menu...')
250
- input()
304
+ run_plugin_management()
251
305
  # Don't break - continue to main menu loop
252
306
 
253
307
  except (KeyboardInterrupt, EOFError):
@@ -259,3 +313,4 @@ def cli(ctx, dev_tools):
259
313
  cli.add_command(plugin)
260
314
  cli.add_command(config)
261
315
  cli.add_command(devtools)
316
+ cli.add_command(code_server)
@@ -0,0 +1,169 @@
1
+ """Code-server integration for remote plugin development."""
2
+
3
+ from typing import Optional
4
+
5
+ import click
6
+
7
+ from synapse_sdk.cli.config import fetch_agents_from_backend, get_agent_config
8
+ from synapse_sdk.devtools.config import get_backend_config
9
+
10
+
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."""
16
+
17
+ # Get current agent configuration
18
+ agent_config = get_agent_config()
19
+ backend_config = get_backend_config()
20
+
21
+ if not backend_config:
22
+ click.echo("❌ No backend configured. Run 'synapse config' first.")
23
+ return
24
+
25
+ # If no agent specified, use current agent or let user choose
26
+ if not agent:
27
+ if agent_config and agent_config.get('id'):
28
+ agent = agent_config['id']
29
+ click.echo(f'Using current agent: {agent_config.get("name", agent)}')
30
+ else:
31
+ # List available agents
32
+ agents, error = fetch_agents_from_backend()
33
+ if not agents:
34
+ click.echo('❌ No agents available. Check your backend configuration.')
35
+ return
36
+
37
+ if len(agents) == 1:
38
+ # If only one agent, use it
39
+ agent = agents[0]['id']
40
+ click.echo(f'Using agent: {agents[0].get("name", agent)}')
41
+ else:
42
+ # Let user choose
43
+ click.echo('Available agents:')
44
+ for i, agent_info in enumerate(agents, 1):
45
+ status = agent_info.get('status_display', 'Unknown')
46
+ name = agent_info.get('name', agent_info['id'])
47
+ click.echo(f' {i}. {name} ({status})')
48
+
49
+ try:
50
+ choice = click.prompt('Select agent', type=int)
51
+ if 1 <= choice <= len(agents):
52
+ agent = agents[choice - 1]['id']
53
+ else:
54
+ click.echo('❌ Invalid selection')
55
+ return
56
+ except (ValueError, EOFError, KeyboardInterrupt):
57
+ click.echo('\n❌ Cancelled')
58
+ return
59
+
60
+ # Connect to agent and get code-server info
61
+ try:
62
+ # Get agent details from backend to get the agent URL
63
+ from synapse_sdk.clients.backend import BackendClient
64
+
65
+ backend_client = BackendClient(backend_config['host'], access_token=backend_config['token'])
66
+
67
+ # Get agent information
68
+ try:
69
+ agent_info = backend_client._get(f'agents/{agent}/')
70
+ except Exception as e:
71
+ click.echo(f'❌ Failed to get agent information for: {agent}')
72
+ click.echo(f'Error: {e}')
73
+ return
74
+
75
+ if not agent_info or not agent_info.get('url'):
76
+ click.echo(f'❌ Agent {agent} does not have a valid URL')
77
+ click.echo(f'Agent info: {agent_info}')
78
+ return
79
+
80
+ # Get the agent token from local configuration
81
+ agent_token = agent_config.get('token')
82
+ if not agent_token:
83
+ click.echo('❌ No agent token found in configuration')
84
+ click.echo("Run 'synapse config' to configure the agent")
85
+ return
86
+
87
+ # Create agent client
88
+ from synapse_sdk.clients.agent import AgentClient
89
+
90
+ client = AgentClient(base_url=agent_info['url'], agent_token=agent_token, user_token=backend_config['token'])
91
+
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
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
+
112
+ # Display connection information
113
+ click.echo('\n✅ Code-Server is available!')
114
+
115
+ workspace = info.get('workspace', '/home/coder/workspace')
116
+
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)')
125
+
126
+ click.echo(f'\n📁 Workspace: {workspace}')
127
+
128
+ # Optionally open in browser
129
+ if open_browser and info.get('url'):
130
+ import subprocess
131
+
132
+ click.echo('\nAttempting to open in browser...')
133
+
134
+ # Try to open browser, suppressing stderr to avoid xdg-open noise
135
+ try:
136
+ # Use subprocess to suppress xdg-open errors
137
+ result = subprocess.run(['xdg-open', info['url']], capture_output=True, text=True, timeout=2)
138
+ if result.returncode != 0:
139
+ click.echo('⚠️ Could not open browser automatically (no display?)')
140
+ click.echo(f'👉 Please manually open: {info["url"]}')
141
+ except (subprocess.TimeoutExpired, FileNotFoundError):
142
+ # xdg-open not available or timed out
143
+ click.echo('⚠️ Could not open browser (headless environment)')
144
+ click.echo(f'👉 Please manually open: {info["url"]}')
145
+ except Exception:
146
+ # Fallback for other errors
147
+ click.echo(f'👉 Please manually open: {info["url"]}')
148
+
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!')
154
+
155
+ except Exception as e:
156
+ import traceback
157
+
158
+ click.echo(f'❌ Failed to connect to agent: {e}')
159
+
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())
164
+
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')
synapse_sdk/cli/config.py CHANGED
@@ -18,6 +18,81 @@ def clear_screen():
18
18
  os.system('cls' if os.name == 'nt' else 'clear')
19
19
 
20
20
 
21
+ def check_backend_connection(host, token):
22
+ """Test backend connection with given credentials"""
23
+ try:
24
+ # Try an authenticated endpoint to verify token validity
25
+ response = requests.get(
26
+ f'{host}/users/me/',
27
+ headers={'Synapse-Access-Token': f'Token {token}'},
28
+ timeout=5,
29
+ )
30
+
31
+ if response.status_code == 200:
32
+ return True, 'Connection successful'
33
+ elif response.status_code == 401:
34
+ return False, 'Invalid token (401)'
35
+ elif response.status_code == 403:
36
+ return False, 'Access forbidden (403)'
37
+ elif response.status_code == 404:
38
+ # If /users/me/ doesn't exist, try /health as fallback
39
+ try:
40
+ health_response = requests.get(
41
+ f'{host}/health',
42
+ headers={'Synapse-Access-Token': f'Token {token}'},
43
+ timeout=3,
44
+ )
45
+ if health_response.status_code == 200:
46
+ return True, 'Connection successful'
47
+ elif health_response.status_code == 401:
48
+ return False, 'Invalid token (401)'
49
+ elif health_response.status_code == 403:
50
+ return False, 'Access forbidden (403)'
51
+ else:
52
+ return False, f'HTTP {health_response.status_code}'
53
+ except Exception:
54
+ return False, 'Endpoint not found (404)'
55
+ else:
56
+ return False, f'HTTP {response.status_code}'
57
+
58
+ except requests.exceptions.Timeout:
59
+ return False, 'Connection timeout (>5s)'
60
+ except requests.exceptions.ConnectionError:
61
+ return False, 'Connection failed'
62
+ except Exception as e:
63
+ return False, f'Connection error: {str(e)}'
64
+
65
+
66
+ def check_agent_connection(agent_url, agent_token):
67
+ """Test agent connection with given credentials"""
68
+ if not agent_url or not agent_token:
69
+ return True, 'Agent configured (no URL/token to test)'
70
+
71
+ try:
72
+ # Try to connect to the agent
73
+ response = requests.get(
74
+ f'{agent_url}/health/',
75
+ headers={'Authorization': agent_token},
76
+ timeout=5,
77
+ )
78
+
79
+ if response.status_code == 200:
80
+ return True, 'Agent connection successful'
81
+ elif response.status_code == 401:
82
+ return False, 'Invalid agent token (401)'
83
+ elif response.status_code == 403:
84
+ return False, 'Agent access forbidden (403)'
85
+ else:
86
+ return False, f'Agent HTTP {response.status_code}'
87
+
88
+ except requests.exceptions.Timeout:
89
+ return False, 'Agent connection timeout (>5s)'
90
+ except requests.exceptions.ConnectionError:
91
+ return False, 'Agent connection failed'
92
+ except Exception as e:
93
+ return False, f'Agent error: {str(e)}'
94
+
95
+
21
96
  def get_agent_config():
22
97
  """Get current agent configuration"""
23
98
  config = load_devtools_config()
@@ -121,7 +196,7 @@ def configure_backend():
121
196
 
122
197
  if backend_config:
123
198
  click.echo(f'Current backend: {backend_config["host"]}')
124
- click.echo(f'Token: {backend_config["token"][:8]}...')
199
+ click.echo(f'Token: {backend_config["token"]}')
125
200
  click.echo()
126
201
 
127
202
  questions = [
@@ -164,7 +239,7 @@ def configure_backend():
164
239
  message='Backend host URL',
165
240
  default=backend_config['host'] if backend_config else 'https://api.synapse.sh',
166
241
  ),
167
- inquirer.Password('token', message='API token'),
242
+ inquirer.Text('token', default=backend_config['token'] if backend_config else '', message='API token'),
168
243
  ]
169
244
 
170
245
  config_answers = inquirer.prompt(config_questions)
@@ -172,7 +247,15 @@ def configure_backend():
172
247
  set_backend_config(config_answers['host'], config_answers['token'])
173
248
  click.echo(click.style('\n✓ Backend configuration saved!', fg='green'))
174
249
  click.echo(f'Host: {config_answers["host"]}')
175
- click.echo(f'Token: {config_answers["token"][:8]}...')
250
+ click.echo(f'Token: {config_answers["token"]}')
251
+
252
+ # Test the connection
253
+ click.echo('\nTesting connection...')
254
+ success, message = check_backend_connection(config_answers['host'], config_answers['token'])
255
+ if success:
256
+ click.echo(click.style(f'🟢 {message}', fg='green'))
257
+ else:
258
+ click.echo(click.style(f'🔴 {message}', fg='red'))
176
259
 
177
260
 
178
261
  def configure_agent():
@@ -233,6 +316,15 @@ def configure_agent():
233
316
  if selected.get('url'):
234
317
  click.echo(f'URL: {selected["url"]}')
235
318
 
319
+ # Test the agent connection if URL and token are provided
320
+ if selected.get('url') and selected.get('token'):
321
+ click.echo('\nTesting agent connection...')
322
+ success, message = check_agent_connection(selected['url'], selected['token'])
323
+ if success:
324
+ click.echo(click.style(f'🟢 {message}', fg='green'))
325
+ else:
326
+ click.echo(click.style(f'🔴 {message}', fg='red'))
327
+
236
328
  if answers['action'] == 'manual':
237
329
  manual_questions = [
238
330
  inquirer.Text('agent_id', message='Agent ID', default=agent_config.get('id', '') if agent_config else ''),
@@ -253,6 +345,15 @@ def configure_agent():
253
345
  click.echo(f'Agent ID: {manual_answers["agent_id"]}')
254
346
  if manual_answers.get('agent_url'):
255
347
  click.echo(f'Agent URL: {manual_answers["agent_url"]}')
348
+
349
+ # Test the agent connection if URL and token are provided
350
+ if manual_answers.get('agent_url') and manual_answers.get('agent_token'):
351
+ click.echo('\nTesting agent connection...')
352
+ success, message = check_agent_connection(manual_answers['agent_url'], manual_answers['agent_token'])
353
+ if success:
354
+ click.echo(click.style(f'🟢 {message}', fg='green'))
355
+ else:
356
+ click.echo(click.style(f'🔴 {message}', fg='red'))
256
357
  return
257
358
 
258
359
 
@@ -268,7 +369,7 @@ def show_current_config():
268
369
  click.echo(click.style('\n🔗 Backend:', fg='blue', bold=True))
269
370
  if backend_config:
270
371
  click.echo(f' Host: {backend_config["host"]}')
271
- click.echo(f' Token: {backend_config["token"][:8]}...')
372
+ click.echo(f' Token: {backend_config["token"]}')
272
373
  click.echo(click.style(' Status: ✓ Configured', fg='green'))
273
374
  else:
274
375
  click.echo(click.style(' Status: ✗ Not configured', fg='red'))