synapse-sdk 1.0.0a11__py3-none-any.whl → 2026.1.1b2__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.
- synapse_sdk/__init__.py +24 -0
- synapse_sdk/cli/__init__.py +9 -8
- synapse_sdk/cli/agent/__init__.py +25 -0
- synapse_sdk/cli/agent/config.py +104 -0
- synapse_sdk/cli/agent/select.py +197 -0
- synapse_sdk/cli/auth.py +104 -0
- synapse_sdk/cli/main.py +1025 -0
- synapse_sdk/cli/plugin/__init__.py +58 -0
- synapse_sdk/cli/plugin/create.py +566 -0
- synapse_sdk/cli/plugin/job.py +196 -0
- synapse_sdk/cli/plugin/publish.py +322 -0
- synapse_sdk/cli/plugin/run.py +131 -0
- synapse_sdk/cli/plugin/test.py +200 -0
- synapse_sdk/clients/README.md +239 -0
- synapse_sdk/clients/__init__.py +5 -0
- synapse_sdk/clients/_template.py +266 -0
- synapse_sdk/clients/agent/__init__.py +84 -29
- synapse_sdk/clients/agent/async_ray.py +289 -0
- synapse_sdk/clients/agent/container.py +83 -0
- synapse_sdk/clients/agent/plugin.py +101 -0
- synapse_sdk/clients/agent/ray.py +296 -39
- synapse_sdk/clients/backend/__init__.py +152 -12
- synapse_sdk/clients/backend/annotation.py +164 -22
- synapse_sdk/clients/backend/core.py +101 -0
- synapse_sdk/clients/backend/data_collection.py +292 -0
- synapse_sdk/clients/backend/hitl.py +87 -0
- synapse_sdk/clients/backend/integration.py +374 -46
- synapse_sdk/clients/backend/ml.py +134 -22
- synapse_sdk/clients/backend/models.py +247 -0
- synapse_sdk/clients/base.py +538 -59
- synapse_sdk/clients/exceptions.py +35 -7
- synapse_sdk/clients/pipeline/__init__.py +5 -0
- synapse_sdk/clients/pipeline/client.py +636 -0
- synapse_sdk/clients/protocols.py +178 -0
- synapse_sdk/clients/utils.py +86 -8
- synapse_sdk/clients/validation.py +58 -0
- synapse_sdk/enums.py +76 -0
- synapse_sdk/exceptions.py +168 -0
- synapse_sdk/integrations/__init__.py +74 -0
- synapse_sdk/integrations/_base.py +119 -0
- synapse_sdk/integrations/_context.py +53 -0
- synapse_sdk/integrations/ultralytics/__init__.py +78 -0
- synapse_sdk/integrations/ultralytics/_callbacks.py +126 -0
- synapse_sdk/integrations/ultralytics/_patches.py +124 -0
- synapse_sdk/loggers.py +476 -95
- synapse_sdk/mcp/MCP.md +69 -0
- synapse_sdk/mcp/__init__.py +48 -0
- synapse_sdk/mcp/__main__.py +6 -0
- synapse_sdk/mcp/config.py +349 -0
- synapse_sdk/mcp/prompts/__init__.py +4 -0
- synapse_sdk/mcp/resources/__init__.py +4 -0
- synapse_sdk/mcp/server.py +1352 -0
- synapse_sdk/mcp/tools/__init__.py +6 -0
- synapse_sdk/plugins/__init__.py +133 -9
- synapse_sdk/plugins/action.py +229 -0
- synapse_sdk/plugins/actions/__init__.py +82 -0
- synapse_sdk/plugins/actions/dataset/__init__.py +37 -0
- synapse_sdk/plugins/actions/dataset/action.py +471 -0
- synapse_sdk/plugins/actions/export/__init__.py +55 -0
- synapse_sdk/plugins/actions/export/action.py +183 -0
- synapse_sdk/plugins/actions/export/context.py +59 -0
- synapse_sdk/plugins/actions/inference/__init__.py +84 -0
- synapse_sdk/plugins/actions/inference/action.py +285 -0
- synapse_sdk/plugins/actions/inference/context.py +81 -0
- synapse_sdk/plugins/actions/inference/deployment.py +322 -0
- synapse_sdk/plugins/actions/inference/serve.py +252 -0
- synapse_sdk/plugins/actions/train/__init__.py +54 -0
- synapse_sdk/plugins/actions/train/action.py +326 -0
- synapse_sdk/plugins/actions/train/context.py +57 -0
- synapse_sdk/plugins/actions/upload/__init__.py +49 -0
- synapse_sdk/plugins/actions/upload/action.py +165 -0
- synapse_sdk/plugins/actions/upload/context.py +61 -0
- synapse_sdk/plugins/config.py +98 -0
- synapse_sdk/plugins/context/__init__.py +109 -0
- synapse_sdk/plugins/context/env.py +113 -0
- synapse_sdk/plugins/datasets/__init__.py +113 -0
- synapse_sdk/plugins/datasets/converters/__init__.py +76 -0
- synapse_sdk/plugins/datasets/converters/base.py +347 -0
- synapse_sdk/plugins/datasets/converters/yolo/__init__.py +9 -0
- synapse_sdk/plugins/datasets/converters/yolo/from_dm.py +468 -0
- synapse_sdk/plugins/datasets/converters/yolo/to_dm.py +381 -0
- synapse_sdk/plugins/datasets/formats/__init__.py +82 -0
- synapse_sdk/plugins/datasets/formats/dm.py +351 -0
- synapse_sdk/plugins/datasets/formats/yolo.py +240 -0
- synapse_sdk/plugins/decorators.py +83 -0
- synapse_sdk/plugins/discovery.py +790 -0
- synapse_sdk/plugins/docs/ACTION_DEV_GUIDE.md +933 -0
- synapse_sdk/plugins/docs/ARCHITECTURE.md +1225 -0
- synapse_sdk/plugins/docs/LOGGING_SYSTEM.md +683 -0
- synapse_sdk/plugins/docs/OVERVIEW.md +531 -0
- synapse_sdk/plugins/docs/PIPELINE_GUIDE.md +145 -0
- synapse_sdk/plugins/docs/README.md +513 -0
- synapse_sdk/plugins/docs/STEP.md +656 -0
- synapse_sdk/plugins/enums.py +70 -10
- synapse_sdk/plugins/errors.py +92 -0
- synapse_sdk/plugins/executors/__init__.py +43 -0
- synapse_sdk/plugins/executors/local.py +99 -0
- synapse_sdk/plugins/executors/ray/__init__.py +18 -0
- synapse_sdk/plugins/executors/ray/base.py +282 -0
- synapse_sdk/plugins/executors/ray/job.py +298 -0
- synapse_sdk/plugins/executors/ray/jobs_api.py +511 -0
- synapse_sdk/plugins/executors/ray/packaging.py +137 -0
- synapse_sdk/plugins/executors/ray/pipeline.py +792 -0
- synapse_sdk/plugins/executors/ray/task.py +257 -0
- synapse_sdk/plugins/models/__init__.py +26 -0
- synapse_sdk/plugins/models/logger.py +173 -0
- synapse_sdk/plugins/models/pipeline.py +25 -0
- synapse_sdk/plugins/pipelines/__init__.py +81 -0
- synapse_sdk/plugins/pipelines/action_pipeline.py +417 -0
- synapse_sdk/plugins/pipelines/context.py +107 -0
- synapse_sdk/plugins/pipelines/display.py +311 -0
- synapse_sdk/plugins/runner.py +114 -0
- synapse_sdk/plugins/schemas/__init__.py +19 -0
- synapse_sdk/plugins/schemas/results.py +152 -0
- synapse_sdk/plugins/steps/__init__.py +63 -0
- synapse_sdk/plugins/steps/base.py +128 -0
- synapse_sdk/plugins/steps/context.py +90 -0
- synapse_sdk/plugins/steps/orchestrator.py +128 -0
- synapse_sdk/plugins/steps/registry.py +103 -0
- synapse_sdk/plugins/steps/utils/__init__.py +20 -0
- synapse_sdk/plugins/steps/utils/logging.py +85 -0
- synapse_sdk/plugins/steps/utils/timing.py +71 -0
- synapse_sdk/plugins/steps/utils/validation.py +68 -0
- synapse_sdk/plugins/templates/__init__.py +50 -0
- synapse_sdk/plugins/templates/base/.gitignore.j2 +26 -0
- synapse_sdk/plugins/templates/base/.synapseignore.j2 +11 -0
- synapse_sdk/plugins/templates/base/README.md.j2 +26 -0
- synapse_sdk/plugins/templates/base/plugin/__init__.py.j2 +1 -0
- synapse_sdk/plugins/templates/base/pyproject.toml.j2 +14 -0
- synapse_sdk/plugins/templates/base/requirements.txt.j2 +1 -0
- synapse_sdk/plugins/templates/custom/plugin/main.py.j2 +18 -0
- synapse_sdk/plugins/templates/data_validation/plugin/validate.py.j2 +32 -0
- synapse_sdk/plugins/templates/export/plugin/export.py.j2 +36 -0
- synapse_sdk/plugins/templates/neural_net/plugin/inference.py.j2 +36 -0
- synapse_sdk/plugins/templates/neural_net/plugin/train.py.j2 +33 -0
- synapse_sdk/plugins/templates/post_annotation/plugin/post_annotate.py.j2 +32 -0
- synapse_sdk/plugins/templates/pre_annotation/plugin/pre_annotate.py.j2 +32 -0
- synapse_sdk/plugins/templates/smart_tool/plugin/auto_label.py.j2 +44 -0
- synapse_sdk/plugins/templates/upload/plugin/upload.py.j2 +35 -0
- synapse_sdk/plugins/testing/__init__.py +25 -0
- synapse_sdk/plugins/testing/sample_actions.py +98 -0
- synapse_sdk/plugins/types.py +206 -0
- synapse_sdk/plugins/upload.py +595 -64
- synapse_sdk/plugins/utils.py +325 -37
- synapse_sdk/shared/__init__.py +25 -0
- synapse_sdk/utils/__init__.py +1 -0
- synapse_sdk/utils/auth.py +74 -0
- synapse_sdk/utils/file/__init__.py +58 -0
- synapse_sdk/utils/file/archive.py +449 -0
- synapse_sdk/utils/file/checksum.py +167 -0
- synapse_sdk/utils/file/download.py +286 -0
- synapse_sdk/utils/file/io.py +129 -0
- synapse_sdk/utils/file/requirements.py +36 -0
- synapse_sdk/utils/network.py +168 -0
- synapse_sdk/utils/storage/__init__.py +238 -0
- synapse_sdk/utils/storage/config.py +188 -0
- synapse_sdk/utils/storage/errors.py +52 -0
- synapse_sdk/utils/storage/providers/__init__.py +13 -0
- synapse_sdk/utils/storage/providers/base.py +76 -0
- synapse_sdk/utils/storage/providers/gcs.py +168 -0
- synapse_sdk/utils/storage/providers/http.py +250 -0
- synapse_sdk/utils/storage/providers/local.py +126 -0
- synapse_sdk/utils/storage/providers/s3.py +177 -0
- synapse_sdk/utils/storage/providers/sftp.py +208 -0
- synapse_sdk/utils/storage/registry.py +125 -0
- synapse_sdk/utils/websocket.py +99 -0
- synapse_sdk-2026.1.1b2.dist-info/METADATA +715 -0
- synapse_sdk-2026.1.1b2.dist-info/RECORD +172 -0
- {synapse_sdk-1.0.0a11.dist-info → synapse_sdk-2026.1.1b2.dist-info}/WHEEL +1 -1
- synapse_sdk-2026.1.1b2.dist-info/licenses/LICENSE +201 -0
- locale/en/LC_MESSAGES/messages.mo +0 -0
- locale/en/LC_MESSAGES/messages.po +0 -39
- locale/ko/LC_MESSAGES/messages.mo +0 -0
- locale/ko/LC_MESSAGES/messages.po +0 -34
- synapse_sdk/cli/create_plugin.py +0 -10
- synapse_sdk/clients/agent/core.py +0 -7
- synapse_sdk/clients/agent/service.py +0 -15
- synapse_sdk/clients/backend/dataset.py +0 -51
- synapse_sdk/clients/ray/__init__.py +0 -6
- synapse_sdk/clients/ray/core.py +0 -22
- synapse_sdk/clients/ray/serve.py +0 -20
- synapse_sdk/i18n.py +0 -35
- synapse_sdk/plugins/categories/__init__.py +0 -0
- synapse_sdk/plugins/categories/base.py +0 -235
- synapse_sdk/plugins/categories/data_validation/__init__.py +0 -0
- synapse_sdk/plugins/categories/data_validation/actions/__init__.py +0 -0
- synapse_sdk/plugins/categories/data_validation/actions/validation.py +0 -10
- synapse_sdk/plugins/categories/data_validation/templates/config.yaml +0 -3
- synapse_sdk/plugins/categories/data_validation/templates/plugin/__init__.py +0 -0
- synapse_sdk/plugins/categories/data_validation/templates/plugin/validation.py +0 -5
- synapse_sdk/plugins/categories/decorators.py +0 -13
- synapse_sdk/plugins/categories/export/__init__.py +0 -0
- synapse_sdk/plugins/categories/export/actions/__init__.py +0 -0
- synapse_sdk/plugins/categories/export/actions/export.py +0 -10
- synapse_sdk/plugins/categories/import/__init__.py +0 -0
- synapse_sdk/plugins/categories/import/actions/__init__.py +0 -0
- synapse_sdk/plugins/categories/import/actions/import.py +0 -10
- synapse_sdk/plugins/categories/neural_net/__init__.py +0 -0
- synapse_sdk/plugins/categories/neural_net/actions/__init__.py +0 -0
- synapse_sdk/plugins/categories/neural_net/actions/deployment.py +0 -45
- synapse_sdk/plugins/categories/neural_net/actions/inference.py +0 -18
- synapse_sdk/plugins/categories/neural_net/actions/test.py +0 -10
- synapse_sdk/plugins/categories/neural_net/actions/train.py +0 -143
- synapse_sdk/plugins/categories/neural_net/templates/config.yaml +0 -12
- synapse_sdk/plugins/categories/neural_net/templates/plugin/__init__.py +0 -0
- synapse_sdk/plugins/categories/neural_net/templates/plugin/inference.py +0 -4
- synapse_sdk/plugins/categories/neural_net/templates/plugin/test.py +0 -2
- synapse_sdk/plugins/categories/neural_net/templates/plugin/train.py +0 -14
- synapse_sdk/plugins/categories/post_annotation/__init__.py +0 -0
- synapse_sdk/plugins/categories/post_annotation/actions/__init__.py +0 -0
- synapse_sdk/plugins/categories/post_annotation/actions/post_annotation.py +0 -10
- synapse_sdk/plugins/categories/post_annotation/templates/config.yaml +0 -3
- synapse_sdk/plugins/categories/post_annotation/templates/plugin/__init__.py +0 -0
- synapse_sdk/plugins/categories/post_annotation/templates/plugin/post_annotation.py +0 -3
- synapse_sdk/plugins/categories/pre_annotation/__init__.py +0 -0
- synapse_sdk/plugins/categories/pre_annotation/actions/__init__.py +0 -0
- synapse_sdk/plugins/categories/pre_annotation/actions/pre_annotation.py +0 -10
- synapse_sdk/plugins/categories/pre_annotation/templates/config.yaml +0 -3
- synapse_sdk/plugins/categories/pre_annotation/templates/plugin/__init__.py +0 -0
- synapse_sdk/plugins/categories/pre_annotation/templates/plugin/pre_annotation.py +0 -3
- synapse_sdk/plugins/categories/registry.py +0 -16
- synapse_sdk/plugins/categories/smart_tool/__init__.py +0 -0
- synapse_sdk/plugins/categories/smart_tool/actions/__init__.py +0 -0
- synapse_sdk/plugins/categories/smart_tool/actions/auto_label.py +0 -37
- synapse_sdk/plugins/categories/smart_tool/templates/config.yaml +0 -7
- synapse_sdk/plugins/categories/smart_tool/templates/plugin/__init__.py +0 -0
- synapse_sdk/plugins/categories/smart_tool/templates/plugin/auto_label.py +0 -11
- synapse_sdk/plugins/categories/templates.py +0 -32
- synapse_sdk/plugins/cli/__init__.py +0 -21
- synapse_sdk/plugins/cli/publish.py +0 -37
- synapse_sdk/plugins/cli/run.py +0 -67
- synapse_sdk/plugins/exceptions.py +0 -22
- synapse_sdk/plugins/models.py +0 -121
- synapse_sdk/plugins/templates/cookiecutter.json +0 -11
- synapse_sdk/plugins/templates/hooks/post_gen_project.py +0 -3
- synapse_sdk/plugins/templates/hooks/pre_prompt.py +0 -21
- synapse_sdk/plugins/templates/synapse-{{cookiecutter.plugin_code}}-plugin/.env +0 -24
- synapse_sdk/plugins/templates/synapse-{{cookiecutter.plugin_code}}-plugin/.env.dist +0 -24
- synapse_sdk/plugins/templates/synapse-{{cookiecutter.plugin_code}}-plugin/.gitignore +0 -27
- synapse_sdk/plugins/templates/synapse-{{cookiecutter.plugin_code}}-plugin/.pre-commit-config.yaml +0 -7
- synapse_sdk/plugins/templates/synapse-{{cookiecutter.plugin_code}}-plugin/README.md +0 -5
- synapse_sdk/plugins/templates/synapse-{{cookiecutter.plugin_code}}-plugin/config.yaml +0 -6
- synapse_sdk/plugins/templates/synapse-{{cookiecutter.plugin_code}}-plugin/main.py +0 -4
- synapse_sdk/plugins/templates/synapse-{{cookiecutter.plugin_code}}-plugin/plugin/__init__.py +0 -0
- synapse_sdk/plugins/templates/synapse-{{cookiecutter.plugin_code}}-plugin/pyproject.toml +0 -13
- synapse_sdk/plugins/templates/synapse-{{cookiecutter.plugin_code}}-plugin/requirements.txt +0 -1
- synapse_sdk/shared/enums.py +0 -8
- synapse_sdk/utils/debug.py +0 -5
- synapse_sdk/utils/file.py +0 -87
- synapse_sdk/utils/module_loading.py +0 -29
- synapse_sdk/utils/pydantic/__init__.py +0 -0
- synapse_sdk/utils/pydantic/config.py +0 -4
- synapse_sdk/utils/pydantic/errors.py +0 -33
- synapse_sdk/utils/pydantic/validators.py +0 -7
- synapse_sdk/utils/storage.py +0 -91
- synapse_sdk/utils/string.py +0 -11
- synapse_sdk-1.0.0a11.dist-info/LICENSE +0 -21
- synapse_sdk-1.0.0a11.dist-info/METADATA +0 -43
- synapse_sdk-1.0.0a11.dist-info/RECORD +0 -111
- {synapse_sdk-1.0.0a11.dist-info → synapse_sdk-2026.1.1b2.dist-info}/entry_points.txt +0 -0
- {synapse_sdk-1.0.0a11.dist-info → synapse_sdk-2026.1.1b2.dist-info}/top_level.txt +0 -0
synapse_sdk/cli/main.py
ADDED
|
@@ -0,0 +1,1025 @@
|
|
|
1
|
+
"""Synapse SDK CLI main entry point."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
from typing import Annotated, Optional
|
|
7
|
+
|
|
8
|
+
import typer
|
|
9
|
+
from rich.console import Console
|
|
10
|
+
from rich.panel import Panel
|
|
11
|
+
|
|
12
|
+
cli = typer.Typer(
|
|
13
|
+
name='synapse',
|
|
14
|
+
help='Synapse SDK CLI.',
|
|
15
|
+
no_args_is_help=True,
|
|
16
|
+
rich_markup_mode='rich',
|
|
17
|
+
)
|
|
18
|
+
console = Console()
|
|
19
|
+
err_console = Console(stderr=True)
|
|
20
|
+
|
|
21
|
+
# Plugin subcommand group
|
|
22
|
+
plugin_app = typer.Typer(help='Plugin development commands.')
|
|
23
|
+
cli.add_typer(plugin_app, name='plugin')
|
|
24
|
+
|
|
25
|
+
# Job subcommand group (under plugin)
|
|
26
|
+
job_app = typer.Typer(help='Job management commands.')
|
|
27
|
+
plugin_app.add_typer(job_app, name='job')
|
|
28
|
+
|
|
29
|
+
# Agent subcommand group
|
|
30
|
+
agent_app = typer.Typer(help='Agent configuration commands.')
|
|
31
|
+
cli.add_typer(agent_app, name='agent')
|
|
32
|
+
|
|
33
|
+
# MCP subcommand group
|
|
34
|
+
mcp_app = typer.Typer(help='MCP server commands.')
|
|
35
|
+
cli.add_typer(mcp_app, name='mcp')
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
@job_app.command('get')
|
|
39
|
+
def job_get(
|
|
40
|
+
job_id: Annotated[
|
|
41
|
+
str,
|
|
42
|
+
typer.Argument(help='Job ID (UUID) to get details for.'),
|
|
43
|
+
],
|
|
44
|
+
host: Annotated[
|
|
45
|
+
Optional[str],
|
|
46
|
+
typer.Option('--host', help='Synapse API host.'),
|
|
47
|
+
] = None,
|
|
48
|
+
token: Annotated[
|
|
49
|
+
Optional[str],
|
|
50
|
+
typer.Option('--token', '-t', help='Access token.'),
|
|
51
|
+
] = None,
|
|
52
|
+
) -> None:
|
|
53
|
+
"""Get job details.
|
|
54
|
+
|
|
55
|
+
[bold]Examples:[/bold]
|
|
56
|
+
|
|
57
|
+
synapse plugin job get 123
|
|
58
|
+
"""
|
|
59
|
+
from synapse_sdk.cli.auth import get_auth_config
|
|
60
|
+
from synapse_sdk.cli.plugin.job import display_job, get_job
|
|
61
|
+
from synapse_sdk.plugins.errors import PluginError
|
|
62
|
+
|
|
63
|
+
try:
|
|
64
|
+
auth = get_auth_config(host=host, token=token, console=console, interactive=False)
|
|
65
|
+
job = get_job(job_id, auth, console)
|
|
66
|
+
display_job(job, console)
|
|
67
|
+
|
|
68
|
+
except PluginError as e:
|
|
69
|
+
err_console.print(f'[red]Error:[/red] {e.message}')
|
|
70
|
+
raise typer.Exit(1)
|
|
71
|
+
except Exception as e:
|
|
72
|
+
err_console.print(f'[red]Error:[/red] {e}')
|
|
73
|
+
raise typer.Exit(1)
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
@job_app.command('logs')
|
|
77
|
+
def job_logs(
|
|
78
|
+
job_id: Annotated[
|
|
79
|
+
str,
|
|
80
|
+
typer.Argument(help='Job ID (UUID) to get logs for.'),
|
|
81
|
+
],
|
|
82
|
+
follow: Annotated[
|
|
83
|
+
bool,
|
|
84
|
+
typer.Option('--follow', '-f', help='Follow log output (stream).'),
|
|
85
|
+
] = False,
|
|
86
|
+
host: Annotated[
|
|
87
|
+
Optional[str],
|
|
88
|
+
typer.Option('--host', help='Synapse API host.'),
|
|
89
|
+
] = None,
|
|
90
|
+
token: Annotated[
|
|
91
|
+
Optional[str],
|
|
92
|
+
typer.Option('--token', '-t', help='Access token.'),
|
|
93
|
+
] = None,
|
|
94
|
+
) -> None:
|
|
95
|
+
"""Get job logs.
|
|
96
|
+
|
|
97
|
+
[bold]Examples:[/bold]
|
|
98
|
+
|
|
99
|
+
synapse plugin job logs 123
|
|
100
|
+
synapse plugin job logs 123 -f
|
|
101
|
+
"""
|
|
102
|
+
from synapse_sdk.cli.auth import get_auth_config
|
|
103
|
+
from synapse_sdk.cli.plugin.job import get_job_logs, tail_job_logs
|
|
104
|
+
from synapse_sdk.plugins.errors import PluginError
|
|
105
|
+
|
|
106
|
+
try:
|
|
107
|
+
auth = get_auth_config(host=host, token=token, console=console, interactive=False)
|
|
108
|
+
|
|
109
|
+
if follow:
|
|
110
|
+
tail_job_logs(job_id, auth, console)
|
|
111
|
+
else:
|
|
112
|
+
logs = get_job_logs(job_id, auth, console)
|
|
113
|
+
# Display logs
|
|
114
|
+
if isinstance(logs, list):
|
|
115
|
+
for line in logs:
|
|
116
|
+
if line:
|
|
117
|
+
console.print(line)
|
|
118
|
+
elif isinstance(logs, dict):
|
|
119
|
+
for entry in logs.get('results', []):
|
|
120
|
+
console.print(entry.get('message', ''))
|
|
121
|
+
else:
|
|
122
|
+
console.print(logs)
|
|
123
|
+
|
|
124
|
+
except PluginError as e:
|
|
125
|
+
err_console.print(f'[red]Error:[/red] {e.message}')
|
|
126
|
+
raise typer.Exit(1)
|
|
127
|
+
except Exception as e:
|
|
128
|
+
err_console.print(f'[red]Error:[/red] {e}')
|
|
129
|
+
raise typer.Exit(1)
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
@plugin_app.command('publish')
|
|
133
|
+
def plugin_publish(
|
|
134
|
+
path: Annotated[
|
|
135
|
+
Optional[Path],
|
|
136
|
+
typer.Option('--path', '-p', help='Plugin directory (default: current).'),
|
|
137
|
+
] = None,
|
|
138
|
+
config: Annotated[
|
|
139
|
+
Optional[Path],
|
|
140
|
+
typer.Option('--config', '-c', help='Config file path.'),
|
|
141
|
+
] = None,
|
|
142
|
+
host: Annotated[
|
|
143
|
+
Optional[str],
|
|
144
|
+
typer.Option('--host', help='Synapse API host.'),
|
|
145
|
+
] = None,
|
|
146
|
+
token: Annotated[
|
|
147
|
+
Optional[str],
|
|
148
|
+
typer.Option('--token', '-t', help='Access token.'),
|
|
149
|
+
] = None,
|
|
150
|
+
dry_run: Annotated[
|
|
151
|
+
bool,
|
|
152
|
+
typer.Option('--dry-run', help='Preview without uploading.'),
|
|
153
|
+
] = False,
|
|
154
|
+
debug: Annotated[
|
|
155
|
+
bool,
|
|
156
|
+
typer.Option('--debug', help='Debug mode (bypasses backend validation).'),
|
|
157
|
+
] = False,
|
|
158
|
+
yes: Annotated[
|
|
159
|
+
bool,
|
|
160
|
+
typer.Option('--yes', '-y', help='Skip confirmation.'),
|
|
161
|
+
] = False,
|
|
162
|
+
) -> None:
|
|
163
|
+
"""Publish a plugin release to Synapse.
|
|
164
|
+
|
|
165
|
+
Archives plugin files and creates a new release.
|
|
166
|
+
|
|
167
|
+
[bold]Examples:[/bold]
|
|
168
|
+
|
|
169
|
+
synapse plugin publish
|
|
170
|
+
synapse plugin publish -p ./my-plugin --dry-run
|
|
171
|
+
"""
|
|
172
|
+
import questionary
|
|
173
|
+
|
|
174
|
+
from synapse_sdk.cli.auth import get_auth_config
|
|
175
|
+
from synapse_sdk.cli.plugin.publish import publish_plugin
|
|
176
|
+
from synapse_sdk.plugins.errors import PluginError
|
|
177
|
+
|
|
178
|
+
# Resolve path
|
|
179
|
+
plugin_path = (path or Path.cwd()).resolve()
|
|
180
|
+
|
|
181
|
+
if not plugin_path.exists():
|
|
182
|
+
err_console.print(f'[red]Error:[/red] Path not found: {plugin_path}')
|
|
183
|
+
raise typer.Exit(1)
|
|
184
|
+
|
|
185
|
+
try:
|
|
186
|
+
# Get authentication (never interactive - require login first)
|
|
187
|
+
auth = get_auth_config(
|
|
188
|
+
host=host,
|
|
189
|
+
token=token,
|
|
190
|
+
console=console,
|
|
191
|
+
interactive=False,
|
|
192
|
+
)
|
|
193
|
+
|
|
194
|
+
# Confirmation prompt
|
|
195
|
+
if not yes and not dry_run:
|
|
196
|
+
if not questionary.confirm(
|
|
197
|
+
f'Publish plugin to {auth.host}?',
|
|
198
|
+
default=True,
|
|
199
|
+
).ask():
|
|
200
|
+
console.print('[yellow]Cancelled[/yellow]')
|
|
201
|
+
raise typer.Exit(0)
|
|
202
|
+
|
|
203
|
+
# Execute publish
|
|
204
|
+
result = publish_plugin(
|
|
205
|
+
path=plugin_path,
|
|
206
|
+
auth=auth,
|
|
207
|
+
console=console,
|
|
208
|
+
config_path=config,
|
|
209
|
+
dry_run=dry_run,
|
|
210
|
+
debug=debug,
|
|
211
|
+
)
|
|
212
|
+
|
|
213
|
+
# Success output
|
|
214
|
+
if not dry_run:
|
|
215
|
+
console.print(
|
|
216
|
+
Panel(
|
|
217
|
+
f'[green]Published successfully![/green]\n\n'
|
|
218
|
+
f'Release ID: [bold]{result.release_id}[/bold]\n'
|
|
219
|
+
f'Version: [bold]{result.version}[/bold]\n'
|
|
220
|
+
f'Checksum: [dim]{result.checksum[:12]}...[/dim]',
|
|
221
|
+
title='Success',
|
|
222
|
+
border_style='green',
|
|
223
|
+
)
|
|
224
|
+
)
|
|
225
|
+
|
|
226
|
+
except typer.BadParameter as e:
|
|
227
|
+
err_console.print(f'[red]Error:[/red] {e.message}')
|
|
228
|
+
raise typer.Exit(1)
|
|
229
|
+
except PluginError as e:
|
|
230
|
+
err_console.print(f'[red]Error:[/red] {e.message}')
|
|
231
|
+
if e.details:
|
|
232
|
+
err_console.print(f'[dim]Details:[/dim] {e.details}')
|
|
233
|
+
raise typer.Exit(1)
|
|
234
|
+
except FileNotFoundError as e:
|
|
235
|
+
err_console.print(f'[red]Error:[/red] {e}')
|
|
236
|
+
raise typer.Exit(1)
|
|
237
|
+
except Exception as e:
|
|
238
|
+
err_console.print(f'[red]Unexpected error:[/red] {e}')
|
|
239
|
+
raise typer.Exit(1)
|
|
240
|
+
|
|
241
|
+
|
|
242
|
+
@plugin_app.command('create')
|
|
243
|
+
def plugin_create(
|
|
244
|
+
path: Annotated[
|
|
245
|
+
Optional[Path],
|
|
246
|
+
typer.Option('--path', '-p', help='Output directory (default: current).'),
|
|
247
|
+
] = None,
|
|
248
|
+
name: Annotated[
|
|
249
|
+
Optional[str],
|
|
250
|
+
typer.Option('--name', '-n', help='Plugin name.'),
|
|
251
|
+
] = None,
|
|
252
|
+
code: Annotated[
|
|
253
|
+
Optional[str],
|
|
254
|
+
typer.Option('--code', help='Plugin code (slug).'),
|
|
255
|
+
] = None,
|
|
256
|
+
category: Annotated[
|
|
257
|
+
Optional[str],
|
|
258
|
+
typer.Option('--category', '-c', help='Plugin category.'),
|
|
259
|
+
] = None,
|
|
260
|
+
yes: Annotated[
|
|
261
|
+
bool,
|
|
262
|
+
typer.Option('--yes', '-y', help='Skip confirmation.'),
|
|
263
|
+
] = False,
|
|
264
|
+
) -> None:
|
|
265
|
+
"""Create a new plugin from template.
|
|
266
|
+
|
|
267
|
+
Interactively creates a new plugin with the specified category.
|
|
268
|
+
|
|
269
|
+
[bold]Examples:[/bold]
|
|
270
|
+
|
|
271
|
+
synapse plugin create
|
|
272
|
+
synapse plugin create --name "My Plugin" --category neural_net
|
|
273
|
+
"""
|
|
274
|
+
from synapse_sdk.cli.plugin.create import create_plugin_interactive
|
|
275
|
+
|
|
276
|
+
try:
|
|
277
|
+
output_dir = (path or Path.cwd()).resolve()
|
|
278
|
+
|
|
279
|
+
result = create_plugin_interactive(
|
|
280
|
+
output_dir=output_dir,
|
|
281
|
+
name=name,
|
|
282
|
+
code=code,
|
|
283
|
+
category=category,
|
|
284
|
+
console=console,
|
|
285
|
+
yes=yes,
|
|
286
|
+
)
|
|
287
|
+
|
|
288
|
+
if result:
|
|
289
|
+
console.print()
|
|
290
|
+
console.print('[bold green]Next steps:[/bold green]')
|
|
291
|
+
console.print(f' cd {result.plugin_dir.name}')
|
|
292
|
+
console.print(' uv sync')
|
|
293
|
+
console.print(' synapse plugin publish --dry-run')
|
|
294
|
+
|
|
295
|
+
except ValueError as e:
|
|
296
|
+
err_console.print(f'[red]Error:[/red] {e}')
|
|
297
|
+
raise typer.Exit(1)
|
|
298
|
+
except Exception as e:
|
|
299
|
+
err_console.print(f'[red]Unexpected error:[/red] {e}')
|
|
300
|
+
raise typer.Exit(1)
|
|
301
|
+
|
|
302
|
+
|
|
303
|
+
@plugin_app.command('update-config')
|
|
304
|
+
def plugin_update_config(
|
|
305
|
+
path: Annotated[
|
|
306
|
+
Optional[Path],
|
|
307
|
+
typer.Option('--path', '-p', help='Plugin directory (default: current).'),
|
|
308
|
+
] = None,
|
|
309
|
+
config: Annotated[
|
|
310
|
+
Optional[Path],
|
|
311
|
+
typer.Option('--config', '-c', help='Config file path.'),
|
|
312
|
+
] = None,
|
|
313
|
+
) -> None:
|
|
314
|
+
"""Auto-discover actions and sync to config.yaml.
|
|
315
|
+
|
|
316
|
+
Scans plugin source files for BaseAction subclasses and updates config.yaml:
|
|
317
|
+
- Discovers new actions from code and adds them to config
|
|
318
|
+
- Syncs entrypoints, input_type, output_type from code
|
|
319
|
+
- Preserves other config fields (description, etc.)
|
|
320
|
+
|
|
321
|
+
This is automatically run during `synapse plugin publish`,
|
|
322
|
+
but can be run manually during development.
|
|
323
|
+
|
|
324
|
+
[bold]Examples:[/bold]
|
|
325
|
+
|
|
326
|
+
synapse plugin update-config
|
|
327
|
+
synapse plugin update-config -p ./my-plugin
|
|
328
|
+
"""
|
|
329
|
+
from synapse_sdk.cli.plugin.publish import find_config_file
|
|
330
|
+
from synapse_sdk.plugins.discovery import PluginDiscovery
|
|
331
|
+
from synapse_sdk.plugins.errors import PluginError
|
|
332
|
+
|
|
333
|
+
# Resolve path
|
|
334
|
+
plugin_path = (path or Path.cwd()).resolve()
|
|
335
|
+
|
|
336
|
+
if not plugin_path.exists():
|
|
337
|
+
err_console.print(f'[red]Error:[/red] Path not found: {plugin_path}')
|
|
338
|
+
raise typer.Exit(1)
|
|
339
|
+
|
|
340
|
+
try:
|
|
341
|
+
import sys
|
|
342
|
+
|
|
343
|
+
# Find config file
|
|
344
|
+
config_file = find_config_file(plugin_path, config)
|
|
345
|
+
console.print(f'[dim]Config file:[/dim] {config_file}')
|
|
346
|
+
|
|
347
|
+
# Add plugin path to sys.path so action classes can be imported
|
|
348
|
+
plugin_dir = str(plugin_path)
|
|
349
|
+
if plugin_dir not in sys.path:
|
|
350
|
+
sys.path.insert(0, plugin_dir)
|
|
351
|
+
|
|
352
|
+
# Load discovery
|
|
353
|
+
discovery = PluginDiscovery.from_path(config_file)
|
|
354
|
+
|
|
355
|
+
# Sync types
|
|
356
|
+
changes = discovery.sync_config_file(config_file)
|
|
357
|
+
|
|
358
|
+
if changes:
|
|
359
|
+
console.print('\n[green]Updated config.yaml:[/green]')
|
|
360
|
+
for action_name, type_info in changes.items():
|
|
361
|
+
console.print(f' [cyan]{action_name}[/cyan]: {type_info}')
|
|
362
|
+
else:
|
|
363
|
+
console.print('[yellow]No changes needed.[/yellow]')
|
|
364
|
+
console.print(
|
|
365
|
+
'[dim]Actions have no input_type/output_type declarations, or config is already up to date.[/dim]'
|
|
366
|
+
)
|
|
367
|
+
|
|
368
|
+
except FileNotFoundError as e:
|
|
369
|
+
err_console.print(f'[red]Error:[/red] {e}')
|
|
370
|
+
raise typer.Exit(1)
|
|
371
|
+
except PluginError as e:
|
|
372
|
+
err_console.print(f'[red]Error:[/red] {e.message}')
|
|
373
|
+
raise typer.Exit(1)
|
|
374
|
+
except Exception as e:
|
|
375
|
+
err_console.print(f'[red]Error:[/red] {e}')
|
|
376
|
+
raise typer.Exit(1)
|
|
377
|
+
|
|
378
|
+
|
|
379
|
+
@plugin_app.command('run')
|
|
380
|
+
def plugin_run(
|
|
381
|
+
action: Annotated[
|
|
382
|
+
str,
|
|
383
|
+
typer.Argument(help='Action to run (e.g., test, train, deploy, infer).'),
|
|
384
|
+
],
|
|
385
|
+
plugin: Annotated[
|
|
386
|
+
Optional[str],
|
|
387
|
+
typer.Option('--plugin', '-p', help='Plugin code. Auto-detects from config.yaml if not provided.'),
|
|
388
|
+
] = None,
|
|
389
|
+
plugin_path: Annotated[
|
|
390
|
+
Optional[Path],
|
|
391
|
+
typer.Option('--path', help='Plugin directory (default: current).'),
|
|
392
|
+
] = None,
|
|
393
|
+
params: Annotated[
|
|
394
|
+
Optional[str],
|
|
395
|
+
typer.Option('--params', help='JSON parameters to pass to the action.'),
|
|
396
|
+
] = None,
|
|
397
|
+
mode: Annotated[
|
|
398
|
+
Optional[str],
|
|
399
|
+
typer.Option('--mode', '-m', help='Executor mode: local, task, job, or remote.'),
|
|
400
|
+
] = None,
|
|
401
|
+
ray_address: Annotated[
|
|
402
|
+
str,
|
|
403
|
+
typer.Option('--ray-address', help='Ray cluster address (for task/job modes).'),
|
|
404
|
+
] = 'auto',
|
|
405
|
+
num_gpus: Annotated[
|
|
406
|
+
Optional[int],
|
|
407
|
+
typer.Option('--gpus', help='Number of GPUs to request.'),
|
|
408
|
+
] = None,
|
|
409
|
+
num_cpus: Annotated[
|
|
410
|
+
Optional[int],
|
|
411
|
+
typer.Option('--cpus', help='Number of CPUs to request.'),
|
|
412
|
+
] = None,
|
|
413
|
+
input_data: Annotated[
|
|
414
|
+
Optional[str],
|
|
415
|
+
typer.Option('--input', '-i', help='JSON input for inference (for infer action).'),
|
|
416
|
+
] = None,
|
|
417
|
+
infer_path: Annotated[
|
|
418
|
+
str,
|
|
419
|
+
typer.Option('--infer-path', help='Inference endpoint path (for infer action).'),
|
|
420
|
+
] = '/',
|
|
421
|
+
host: Annotated[
|
|
422
|
+
Optional[str],
|
|
423
|
+
typer.Option('--host', help='Synapse API host (for remote mode).'),
|
|
424
|
+
] = None,
|
|
425
|
+
token: Annotated[
|
|
426
|
+
Optional[str],
|
|
427
|
+
typer.Option('--token', '-t', help='Access token (for remote mode).'),
|
|
428
|
+
] = None,
|
|
429
|
+
debug: Annotated[
|
|
430
|
+
bool,
|
|
431
|
+
typer.Option('--debug/--no-debug', help='Debug mode (default: enabled).'),
|
|
432
|
+
] = True,
|
|
433
|
+
debug_sdk: Annotated[
|
|
434
|
+
bool,
|
|
435
|
+
typer.Option('--debug-sdk', help='Bundle local SDK with upload (for SDK development).'),
|
|
436
|
+
] = False,
|
|
437
|
+
) -> None:
|
|
438
|
+
"""Run a plugin action.
|
|
439
|
+
|
|
440
|
+
[bold]Executor Modes:[/bold]
|
|
441
|
+
- local: In-process execution (best for debugging)
|
|
442
|
+
- task: Ray Actor execution (no log streaming)
|
|
443
|
+
- job: Ray Jobs API with log streaming (recommended for remote)
|
|
444
|
+
- remote: Run via Synapse backend API (requires auth)
|
|
445
|
+
|
|
446
|
+
[bold]Examples:[/bold]
|
|
447
|
+
|
|
448
|
+
synapse plugin run test
|
|
449
|
+
synapse plugin run test --mode local
|
|
450
|
+
synapse plugin run test --mode task --gpus 1
|
|
451
|
+
synapse plugin run train --mode job --params '{"epochs": 10}'
|
|
452
|
+
synapse plugin run deploy --mode remote
|
|
453
|
+
"""
|
|
454
|
+
import json
|
|
455
|
+
|
|
456
|
+
import questionary
|
|
457
|
+
from rich.panel import Panel
|
|
458
|
+
|
|
459
|
+
from synapse_sdk.plugins.errors import PluginError
|
|
460
|
+
|
|
461
|
+
try:
|
|
462
|
+
path = (plugin_path or Path.cwd()).resolve()
|
|
463
|
+
|
|
464
|
+
if not path.exists():
|
|
465
|
+
err_console.print(f'[red]Error:[/red] Path not found: {path}')
|
|
466
|
+
raise typer.Exit(1)
|
|
467
|
+
|
|
468
|
+
# Parse params JSON
|
|
469
|
+
parsed_params: dict = {}
|
|
470
|
+
if params:
|
|
471
|
+
try:
|
|
472
|
+
parsed_params = json.loads(params)
|
|
473
|
+
except json.JSONDecodeError as e:
|
|
474
|
+
err_console.print(f'[red]Error:[/red] Invalid JSON params: {e}')
|
|
475
|
+
raise typer.Exit(1)
|
|
476
|
+
|
|
477
|
+
# Interactive executor selection if not provided
|
|
478
|
+
if mode is None:
|
|
479
|
+
mode = questionary.select(
|
|
480
|
+
'Select executor mode:',
|
|
481
|
+
choices=[
|
|
482
|
+
questionary.Choice('local - In-process (best for debugging)', value='local'),
|
|
483
|
+
questionary.Choice('task - Ray Actor (no log streaming)', value='task'),
|
|
484
|
+
questionary.Choice('job - Ray Jobs API (with log streaming)', value='job'),
|
|
485
|
+
questionary.Choice('remote - Synapse Backend API', value='remote'),
|
|
486
|
+
],
|
|
487
|
+
default='local',
|
|
488
|
+
).ask()
|
|
489
|
+
|
|
490
|
+
if mode is None:
|
|
491
|
+
console.print('[yellow]Cancelled[/yellow]')
|
|
492
|
+
raise typer.Exit(0)
|
|
493
|
+
|
|
494
|
+
# Validate mode
|
|
495
|
+
if mode not in ('local', 'task', 'job', 'jobs-api', 'remote'):
|
|
496
|
+
err_console.print(f'[red]Error:[/red] Invalid mode: {mode}. Use local, task, job, or remote.')
|
|
497
|
+
raise typer.Exit(1)
|
|
498
|
+
|
|
499
|
+
# jobs-api is an alias for job
|
|
500
|
+
if mode == 'jobs-api':
|
|
501
|
+
mode = 'job'
|
|
502
|
+
|
|
503
|
+
# Handle local/task/job modes
|
|
504
|
+
if mode in ('local', 'task', 'job'):
|
|
505
|
+
from synapse_sdk.cli.plugin.test import test_plugin
|
|
506
|
+
|
|
507
|
+
# For task/job modes, get agent config for Ray address
|
|
508
|
+
resolved_ray_address = ray_address
|
|
509
|
+
if mode in ('task', 'job') and ray_address == 'auto':
|
|
510
|
+
from urllib.parse import urlparse
|
|
511
|
+
|
|
512
|
+
from synapse_sdk.cli.agent.config import get_agent_config
|
|
513
|
+
from synapse_sdk.cli.agent.select import select_agent_interactive
|
|
514
|
+
from synapse_sdk.cli.auth import get_auth_config
|
|
515
|
+
|
|
516
|
+
agent = get_agent_config()
|
|
517
|
+
if not agent:
|
|
518
|
+
console.print('[yellow]No agent configured. Please select one.[/yellow]\n')
|
|
519
|
+
try:
|
|
520
|
+
auth = get_auth_config(host=host, token=token, console=console, interactive=False)
|
|
521
|
+
agent = select_agent_interactive(auth, console)
|
|
522
|
+
if not agent:
|
|
523
|
+
console.print('[yellow]Cancelled[/yellow]')
|
|
524
|
+
raise typer.Exit(0)
|
|
525
|
+
except Exception as e:
|
|
526
|
+
err_console.print(f'[red]Error:[/red] Failed to get agent: {e}')
|
|
527
|
+
err_console.print('[dim]Run `synapse agent select` to configure an agent.[/dim]')
|
|
528
|
+
raise typer.Exit(1)
|
|
529
|
+
|
|
530
|
+
if agent and agent.url:
|
|
531
|
+
# Convert HTTP URL to Ray address (ray://<host>:10001)
|
|
532
|
+
parsed = urlparse(agent.url)
|
|
533
|
+
ray_host = parsed.hostname or 'localhost'
|
|
534
|
+
resolved_ray_address = f'ray://{ray_host}:10001'
|
|
535
|
+
console.print(f'[dim]Using agent:[/dim] {agent.name or agent.id}')
|
|
536
|
+
console.print(f'[dim]Ray address:[/dim] {resolved_ray_address}')
|
|
537
|
+
|
|
538
|
+
result = test_plugin(
|
|
539
|
+
action=action,
|
|
540
|
+
console=console,
|
|
541
|
+
path=path,
|
|
542
|
+
params=parsed_params if parsed_params else None,
|
|
543
|
+
mode=mode,
|
|
544
|
+
ray_address=resolved_ray_address,
|
|
545
|
+
num_gpus=num_gpus,
|
|
546
|
+
num_cpus=num_cpus,
|
|
547
|
+
include_sdk=debug_sdk,
|
|
548
|
+
)
|
|
549
|
+
|
|
550
|
+
# Success output
|
|
551
|
+
console.print(
|
|
552
|
+
Panel(
|
|
553
|
+
f'[green]Action completed![/green]\n\n'
|
|
554
|
+
f'Plugin: [bold]{result.plugin}[/bold]\n'
|
|
555
|
+
f'Action: [bold]{result.action}[/bold]\n'
|
|
556
|
+
f'Mode: [bold]{result.mode}[/bold]',
|
|
557
|
+
title='Success',
|
|
558
|
+
border_style='green',
|
|
559
|
+
)
|
|
560
|
+
)
|
|
561
|
+
|
|
562
|
+
if result.result:
|
|
563
|
+
console.print('\n[bold]Result:[/bold]')
|
|
564
|
+
if isinstance(result.result, dict):
|
|
565
|
+
for key, value in result.result.items():
|
|
566
|
+
console.print(f' {key}: {value}')
|
|
567
|
+
else:
|
|
568
|
+
console.print(f' {result.result}')
|
|
569
|
+
|
|
570
|
+
else: # mode == 'remote'
|
|
571
|
+
from synapse_sdk.cli.agent.config import get_agent_config
|
|
572
|
+
from synapse_sdk.cli.auth import get_auth_config
|
|
573
|
+
from synapse_sdk.cli.plugin.run import resolve_plugin_code, run_plugin
|
|
574
|
+
|
|
575
|
+
# Get auth configuration
|
|
576
|
+
auth = get_auth_config(host=host, token=token, console=console, interactive=False)
|
|
577
|
+
|
|
578
|
+
# Get agent configuration
|
|
579
|
+
agent = get_agent_config()
|
|
580
|
+
if not agent:
|
|
581
|
+
err_console.print('[red]Error:[/red] No agent configured.')
|
|
582
|
+
err_console.print('[dim]Run `synapse agent select` to configure an agent.[/dim]')
|
|
583
|
+
raise typer.Exit(1)
|
|
584
|
+
|
|
585
|
+
# Resolve plugin code
|
|
586
|
+
plugin_code = resolve_plugin_code(plugin, path)
|
|
587
|
+
|
|
588
|
+
# Map short action names to API action names
|
|
589
|
+
action_map = {'infer': 'inference', 'deploy': 'deployment'}
|
|
590
|
+
api_action = action_map.get(action, action)
|
|
591
|
+
|
|
592
|
+
# Handle infer action
|
|
593
|
+
if action == 'infer' and input_data:
|
|
594
|
+
try:
|
|
595
|
+
parsed_input = json.loads(input_data)
|
|
596
|
+
parsed_params['input'] = parsed_input
|
|
597
|
+
parsed_params['path'] = infer_path
|
|
598
|
+
except json.JSONDecodeError as e:
|
|
599
|
+
err_console.print(f'[red]Error:[/red] Invalid JSON input: {e}')
|
|
600
|
+
raise typer.Exit(1)
|
|
601
|
+
|
|
602
|
+
# Execute run
|
|
603
|
+
result = run_plugin(
|
|
604
|
+
action=api_action,
|
|
605
|
+
auth=auth,
|
|
606
|
+
agent=agent,
|
|
607
|
+
console=console,
|
|
608
|
+
plugin=plugin_code,
|
|
609
|
+
params=parsed_params if parsed_params else None,
|
|
610
|
+
debug=debug,
|
|
611
|
+
)
|
|
612
|
+
|
|
613
|
+
# Success output
|
|
614
|
+
console.print(
|
|
615
|
+
Panel(
|
|
616
|
+
f'[green]Action completed![/green]\n\n'
|
|
617
|
+
f'Plugin: [bold]{result.plugin}[/bold]\n'
|
|
618
|
+
f'Action: [bold]{result.action}[/bold]',
|
|
619
|
+
title='Success',
|
|
620
|
+
border_style='green',
|
|
621
|
+
)
|
|
622
|
+
)
|
|
623
|
+
|
|
624
|
+
if result.result:
|
|
625
|
+
console.print(f'[dim]Result:[/dim] {result.result}')
|
|
626
|
+
|
|
627
|
+
except PluginError as e:
|
|
628
|
+
err_console.print(f'[red]Error:[/red] {e.message}')
|
|
629
|
+
if e.details:
|
|
630
|
+
err_console.print(f'[dim]Details:[/dim] {e.details}')
|
|
631
|
+
raise typer.Exit(1)
|
|
632
|
+
except FileNotFoundError as e:
|
|
633
|
+
err_console.print(f'[red]Error:[/red] {e}')
|
|
634
|
+
raise typer.Exit(1)
|
|
635
|
+
except Exception as e:
|
|
636
|
+
err_console.print(f'[red]Unexpected error:[/red] {e}')
|
|
637
|
+
raise typer.Exit(1)
|
|
638
|
+
|
|
639
|
+
|
|
640
|
+
@agent_app.command('select')
|
|
641
|
+
def agent_select(
|
|
642
|
+
host: Annotated[
|
|
643
|
+
Optional[str],
|
|
644
|
+
typer.Option('--host', help='Synapse API host.'),
|
|
645
|
+
] = None,
|
|
646
|
+
token: Annotated[
|
|
647
|
+
Optional[str],
|
|
648
|
+
typer.Option('--token', '-t', help='Access token.'),
|
|
649
|
+
] = None,
|
|
650
|
+
) -> None:
|
|
651
|
+
"""Interactively select an agent.
|
|
652
|
+
|
|
653
|
+
Fetches available agents from the backend and prompts for selection.
|
|
654
|
+
|
|
655
|
+
[bold]Examples:[/bold]
|
|
656
|
+
|
|
657
|
+
synapse agent select
|
|
658
|
+
"""
|
|
659
|
+
from synapse_sdk.cli.agent.select import select_agent_interactive
|
|
660
|
+
from synapse_sdk.cli.auth import get_auth_config
|
|
661
|
+
|
|
662
|
+
try:
|
|
663
|
+
auth = get_auth_config(host=host, token=token, console=console, interactive=False)
|
|
664
|
+
result = select_agent_interactive(auth, console)
|
|
665
|
+
|
|
666
|
+
if not result:
|
|
667
|
+
console.print('[yellow]Cancelled[/yellow]')
|
|
668
|
+
raise typer.Exit(0)
|
|
669
|
+
|
|
670
|
+
except typer.BadParameter as e:
|
|
671
|
+
err_console.print(f'[red]Error:[/red] {e.message}')
|
|
672
|
+
raise typer.Exit(1)
|
|
673
|
+
except Exception as e:
|
|
674
|
+
err_console.print(f'[red]Error:[/red] {e}')
|
|
675
|
+
raise typer.Exit(1)
|
|
676
|
+
|
|
677
|
+
|
|
678
|
+
@agent_app.command('show')
|
|
679
|
+
def agent_show() -> None:
|
|
680
|
+
"""Show current agent configuration.
|
|
681
|
+
|
|
682
|
+
[bold]Examples:[/bold]
|
|
683
|
+
|
|
684
|
+
synapse agent show
|
|
685
|
+
"""
|
|
686
|
+
from rich.table import Table
|
|
687
|
+
|
|
688
|
+
from synapse_sdk.cli.agent.config import get_agent_config
|
|
689
|
+
|
|
690
|
+
agent = get_agent_config()
|
|
691
|
+
|
|
692
|
+
if not agent:
|
|
693
|
+
console.print('[yellow]No agent configured.[/yellow]')
|
|
694
|
+
console.print('[dim]Use `synapse agent select` or `synapse agent set` to configure.[/dim]')
|
|
695
|
+
raise typer.Exit(0)
|
|
696
|
+
|
|
697
|
+
table = Table(title='Current Agent', show_header=False, box=None, padding=(0, 2))
|
|
698
|
+
table.add_column('Key', style='dim')
|
|
699
|
+
table.add_column('Value')
|
|
700
|
+
table.add_row('ID', str(agent.id))
|
|
701
|
+
table.add_row('Name', agent.name or '-')
|
|
702
|
+
table.add_row('URL', agent.url or '-')
|
|
703
|
+
table.add_row('Token', (agent.token[:12] + '...') if agent.token else '-')
|
|
704
|
+
|
|
705
|
+
console.print(table)
|
|
706
|
+
|
|
707
|
+
|
|
708
|
+
@agent_app.command('clear')
|
|
709
|
+
def agent_clear(
|
|
710
|
+
yes: Annotated[
|
|
711
|
+
bool,
|
|
712
|
+
typer.Option('--yes', '-y', help='Skip confirmation.'),
|
|
713
|
+
] = False,
|
|
714
|
+
) -> None:
|
|
715
|
+
"""Clear agent configuration.
|
|
716
|
+
|
|
717
|
+
[bold]Examples:[/bold]
|
|
718
|
+
|
|
719
|
+
synapse agent clear
|
|
720
|
+
synapse agent clear -y
|
|
721
|
+
"""
|
|
722
|
+
import questionary
|
|
723
|
+
|
|
724
|
+
from synapse_sdk.cli.agent.config import clear_agent_config, get_agent_config
|
|
725
|
+
|
|
726
|
+
agent = get_agent_config()
|
|
727
|
+
|
|
728
|
+
if not agent:
|
|
729
|
+
console.print('[yellow]No agent configured.[/yellow]')
|
|
730
|
+
raise typer.Exit(0)
|
|
731
|
+
|
|
732
|
+
if not yes:
|
|
733
|
+
confirmed = questionary.confirm(
|
|
734
|
+
f'Clear agent configuration for "{agent.name or agent.id}"?',
|
|
735
|
+
default=False,
|
|
736
|
+
).ask()
|
|
737
|
+
|
|
738
|
+
if not confirmed:
|
|
739
|
+
console.print('[yellow]Cancelled[/yellow]')
|
|
740
|
+
raise typer.Exit(0)
|
|
741
|
+
|
|
742
|
+
clear_agent_config()
|
|
743
|
+
console.print('[green]Agent configuration cleared.[/green]')
|
|
744
|
+
|
|
745
|
+
|
|
746
|
+
@mcp_app.command('serve')
|
|
747
|
+
def mcp_serve(
|
|
748
|
+
config: Annotated[
|
|
749
|
+
Optional[Path],
|
|
750
|
+
typer.Option('--config', '-c', help='Path to config file (default: ~/.synapse/config.json).'),
|
|
751
|
+
] = None,
|
|
752
|
+
) -> None:
|
|
753
|
+
"""Start the MCP server for AI assistant integration.
|
|
754
|
+
|
|
755
|
+
Runs the Synapse MCP server which provides tools for:
|
|
756
|
+
- Managing environments (prod, test, demo, local)
|
|
757
|
+
- Listing and running plugins
|
|
758
|
+
- Viewing job logs and status
|
|
759
|
+
- Managing Ray Serve deployments
|
|
760
|
+
|
|
761
|
+
[bold]Configuration:[/bold]
|
|
762
|
+
|
|
763
|
+
Run `synapse mcp init` to create ~/.synapse/config.json
|
|
764
|
+
(or run `synapse login` first)
|
|
765
|
+
|
|
766
|
+
default_environment: prod
|
|
767
|
+
|
|
768
|
+
environments:
|
|
769
|
+
prod:
|
|
770
|
+
backend_url: https://api.synapse.sh
|
|
771
|
+
access_token: your-token
|
|
772
|
+
# Agent is set via list_agents() + select_agent() tools
|
|
773
|
+
|
|
774
|
+
[bold]Cursor Setup:[/bold]
|
|
775
|
+
|
|
776
|
+
Add to ~/.cursor/mcp.json:
|
|
777
|
+
|
|
778
|
+
{
|
|
779
|
+
"mcpServers": {
|
|
780
|
+
"synapse": {
|
|
781
|
+
"command": "uvx",
|
|
782
|
+
"args": ["--from", "synapse-sdk[mcp]", "synapse", "mcp", "serve"]
|
|
783
|
+
}
|
|
784
|
+
}
|
|
785
|
+
}
|
|
786
|
+
|
|
787
|
+
[bold]Claude Code Setup:[/bold]
|
|
788
|
+
|
|
789
|
+
claude mcp add synapse -- uvx --from 'synapse-sdk[mcp]' synapse mcp serve
|
|
790
|
+
|
|
791
|
+
[bold]Examples:[/bold]
|
|
792
|
+
|
|
793
|
+
synapse mcp serve
|
|
794
|
+
synapse mcp serve --config ~/my-config.json
|
|
795
|
+
"""
|
|
796
|
+
try:
|
|
797
|
+
from synapse_sdk.mcp import serve
|
|
798
|
+
from synapse_sdk.mcp.config import ConfigManager
|
|
799
|
+
|
|
800
|
+
# Initialize config manager with custom path if provided
|
|
801
|
+
if config:
|
|
802
|
+
from synapse_sdk.mcp.config import reset_config_manager
|
|
803
|
+
|
|
804
|
+
reset_config_manager()
|
|
805
|
+
# Import and set up with custom path
|
|
806
|
+
import synapse_sdk.mcp.config as config_module
|
|
807
|
+
|
|
808
|
+
config_module._config_manager = ConfigManager(config_path=config)
|
|
809
|
+
|
|
810
|
+
serve()
|
|
811
|
+
|
|
812
|
+
except ImportError:
|
|
813
|
+
err_console.print('[red]Error:[/red] MCP dependencies not installed.')
|
|
814
|
+
err_console.print('[dim]Install with: pip install synapse-sdk[mcp][/dim]')
|
|
815
|
+
raise typer.Exit(1)
|
|
816
|
+
except Exception as e:
|
|
817
|
+
err_console.print(f'[red]Error:[/red] {e}')
|
|
818
|
+
raise typer.Exit(1)
|
|
819
|
+
|
|
820
|
+
|
|
821
|
+
@mcp_app.command('init')
|
|
822
|
+
def mcp_init(
|
|
823
|
+
config: Annotated[
|
|
824
|
+
Optional[Path],
|
|
825
|
+
typer.Option('--config', '-c', help='Path to config file (default: ~/.synapse/config.json).'),
|
|
826
|
+
] = None,
|
|
827
|
+
force: Annotated[
|
|
828
|
+
bool,
|
|
829
|
+
typer.Option('--force', '-f', help='Overwrite existing config file.'),
|
|
830
|
+
] = False,
|
|
831
|
+
) -> None:
|
|
832
|
+
"""Initialize MCP configuration file with example environments.
|
|
833
|
+
|
|
834
|
+
[bold]Examples:[/bold]
|
|
835
|
+
|
|
836
|
+
synapse mcp init
|
|
837
|
+
synapse mcp init --force
|
|
838
|
+
"""
|
|
839
|
+
import json
|
|
840
|
+
|
|
841
|
+
import questionary
|
|
842
|
+
|
|
843
|
+
from synapse_sdk.cli.auth import CONFIG_FILE, load_credentials_file
|
|
844
|
+
|
|
845
|
+
config_path = config or (Path.home() / '.synapse' / 'config.json')
|
|
846
|
+
|
|
847
|
+
if config_path.exists() and not force:
|
|
848
|
+
console.print(f'[yellow]Config file already exists:[/yellow] {config_path}')
|
|
849
|
+
console.print('[dim]Use --force to overwrite.[/dim]')
|
|
850
|
+
raise typer.Exit(0)
|
|
851
|
+
|
|
852
|
+
# Check for existing credentials in config.json
|
|
853
|
+
backend_url = 'https://api.synapse.example.com'
|
|
854
|
+
access_token = 'your-access-token'
|
|
855
|
+
use_existing = False
|
|
856
|
+
|
|
857
|
+
if CONFIG_FILE.exists():
|
|
858
|
+
creds = load_credentials_file()
|
|
859
|
+
existing_host = creds.get('SYNAPSE_HOST')
|
|
860
|
+
existing_token = creds.get('SYNAPSE_ACCESS_TOKEN')
|
|
861
|
+
|
|
862
|
+
if existing_host or existing_token:
|
|
863
|
+
console.print(f'[dim]Found existing credentials at {CONFIG_FILE}[/dim]')
|
|
864
|
+
if existing_host:
|
|
865
|
+
console.print(f' Host: {existing_host}')
|
|
866
|
+
if existing_token:
|
|
867
|
+
console.print(f' Token: {existing_token[:12]}...')
|
|
868
|
+
|
|
869
|
+
use_existing = questionary.confirm(
|
|
870
|
+
'Use existing credentials for MCP?',
|
|
871
|
+
default=True,
|
|
872
|
+
).ask()
|
|
873
|
+
|
|
874
|
+
if use_existing:
|
|
875
|
+
if existing_host:
|
|
876
|
+
backend_url = existing_host
|
|
877
|
+
if existing_token:
|
|
878
|
+
access_token = existing_token
|
|
879
|
+
|
|
880
|
+
# Create config
|
|
881
|
+
example_config = {
|
|
882
|
+
'host': backend_url,
|
|
883
|
+
'access_token': access_token,
|
|
884
|
+
'default_environment': 'default',
|
|
885
|
+
'environments': {
|
|
886
|
+
'default': {
|
|
887
|
+
'backend_url': backend_url,
|
|
888
|
+
'access_token': access_token,
|
|
889
|
+
},
|
|
890
|
+
},
|
|
891
|
+
}
|
|
892
|
+
|
|
893
|
+
# Ensure directory exists
|
|
894
|
+
config_path.parent.mkdir(parents=True, exist_ok=True)
|
|
895
|
+
|
|
896
|
+
# Write config
|
|
897
|
+
config_path.write_text(json.dumps(example_config, indent=2))
|
|
898
|
+
config_path.chmod(0o600)
|
|
899
|
+
|
|
900
|
+
console.print(f'[green]Created config file:[/green] {config_path}')
|
|
901
|
+
console.print()
|
|
902
|
+
|
|
903
|
+
if use_existing:
|
|
904
|
+
console.print('[bold]Next steps:[/bold]')
|
|
905
|
+
console.print(' 1. Run: synapse mcp serve')
|
|
906
|
+
console.print(' 2. Use list_agents() and select_agent() to configure agent')
|
|
907
|
+
else:
|
|
908
|
+
console.print('[bold]Next steps:[/bold]')
|
|
909
|
+
console.print(f' 1. Edit {config_path} with your credentials')
|
|
910
|
+
console.print(' 2. Run: synapse mcp serve')
|
|
911
|
+
|
|
912
|
+
console.print()
|
|
913
|
+
console.print('[bold]Cursor setup:[/bold]')
|
|
914
|
+
console.print(' Add to ~/.cursor/mcp.json:')
|
|
915
|
+
console.print()
|
|
916
|
+
console.print(' {')
|
|
917
|
+
console.print(' "mcpServers": {')
|
|
918
|
+
console.print(' "synapse": {')
|
|
919
|
+
console.print(' "command": "uvx",')
|
|
920
|
+
console.print(' "args": ["--from", "synapse-sdk[mcp]", "synapse", "mcp", "serve"]')
|
|
921
|
+
console.print(' }')
|
|
922
|
+
console.print(' }')
|
|
923
|
+
console.print(' }')
|
|
924
|
+
console.print()
|
|
925
|
+
console.print('[bold]Claude Code setup:[/bold]')
|
|
926
|
+
console.print(" claude mcp add synapse -- uvx --from 'synapse-sdk[mcp]' synapse mcp serve")
|
|
927
|
+
console.print()
|
|
928
|
+
console.print('[dim]For local development:[/dim]')
|
|
929
|
+
console.print(' claude mcp add synapse -- uv run --directory <path-to-synapse-sdk> synapse mcp serve')
|
|
930
|
+
|
|
931
|
+
|
|
932
|
+
@cli.command('login')
|
|
933
|
+
def login(
|
|
934
|
+
host: Annotated[
|
|
935
|
+
Optional[str],
|
|
936
|
+
typer.Option('--host', help='Synapse API host.'),
|
|
937
|
+
] = None,
|
|
938
|
+
token: Annotated[
|
|
939
|
+
Optional[str],
|
|
940
|
+
typer.Option('--token', '-t', help='Access token (will prompt if not provided).'),
|
|
941
|
+
] = None,
|
|
942
|
+
) -> None:
|
|
943
|
+
"""Authenticate with Synapse.
|
|
944
|
+
|
|
945
|
+
Saves credentials to ~/.synapse/config.json for future use.
|
|
946
|
+
|
|
947
|
+
[bold]Examples:[/bold]
|
|
948
|
+
|
|
949
|
+
synapse login
|
|
950
|
+
synapse login --token YOUR_TOKEN
|
|
951
|
+
"""
|
|
952
|
+
import json
|
|
953
|
+
|
|
954
|
+
import questionary
|
|
955
|
+
|
|
956
|
+
from synapse_sdk.cli.auth import DEFAULT_HOST
|
|
957
|
+
|
|
958
|
+
# Prompt for host if not provided
|
|
959
|
+
if not host:
|
|
960
|
+
host = questionary.text(
|
|
961
|
+
'Synapse API host:',
|
|
962
|
+
default=DEFAULT_HOST,
|
|
963
|
+
).ask()
|
|
964
|
+
|
|
965
|
+
if not host:
|
|
966
|
+
console.print('[yellow]Cancelled[/yellow]')
|
|
967
|
+
raise typer.Exit(0)
|
|
968
|
+
|
|
969
|
+
# Prompt for token if not provided
|
|
970
|
+
if not token:
|
|
971
|
+
token = questionary.text(
|
|
972
|
+
'Enter your Synapse access token:',
|
|
973
|
+
validate=lambda x: len(x) > 0 or 'Token cannot be empty',
|
|
974
|
+
).ask()
|
|
975
|
+
|
|
976
|
+
if not token:
|
|
977
|
+
console.print('[yellow]Cancelled[/yellow]')
|
|
978
|
+
raise typer.Exit(0)
|
|
979
|
+
|
|
980
|
+
# Save to config.json
|
|
981
|
+
config_dir = Path.home() / '.synapse'
|
|
982
|
+
config_dir.mkdir(exist_ok=True)
|
|
983
|
+
|
|
984
|
+
config_file = config_dir / 'config.json'
|
|
985
|
+
config = {}
|
|
986
|
+
if config_file.exists():
|
|
987
|
+
try:
|
|
988
|
+
config = json.loads(config_file.read_text())
|
|
989
|
+
except json.JSONDecodeError:
|
|
990
|
+
pass
|
|
991
|
+
config['host'] = host
|
|
992
|
+
config['access_token'] = token
|
|
993
|
+
config_file.write_text(json.dumps(config, indent=2))
|
|
994
|
+
config_file.chmod(0o600)
|
|
995
|
+
|
|
996
|
+
console.print('[green]Logged in successfully![/green]')
|
|
997
|
+
console.print(f'[dim]Credentials saved to {config_file}[/dim]')
|
|
998
|
+
|
|
999
|
+
|
|
1000
|
+
def version_callback(value: bool) -> None:
|
|
1001
|
+
"""Show version and exit."""
|
|
1002
|
+
if value:
|
|
1003
|
+
from importlib.metadata import version
|
|
1004
|
+
|
|
1005
|
+
try:
|
|
1006
|
+
ver = version('synapse-sdk')
|
|
1007
|
+
except Exception:
|
|
1008
|
+
ver = 'unknown'
|
|
1009
|
+
console.print(f'synapse-sdk [bold cyan]{ver}[/bold cyan]')
|
|
1010
|
+
raise typer.Exit()
|
|
1011
|
+
|
|
1012
|
+
|
|
1013
|
+
@cli.callback()
|
|
1014
|
+
def main(
|
|
1015
|
+
version: Annotated[
|
|
1016
|
+
Optional[bool],
|
|
1017
|
+
typer.Option('--version', '-v', callback=version_callback, is_eager=True, help='Show version and exit.'),
|
|
1018
|
+
] = None,
|
|
1019
|
+
) -> None:
|
|
1020
|
+
"""Synapse SDK CLI."""
|
|
1021
|
+
pass
|
|
1022
|
+
|
|
1023
|
+
|
|
1024
|
+
if __name__ == '__main__':
|
|
1025
|
+
cli()
|