mlspace-plugin 0.2.1__tar.gz → 0.2.2__tar.gz
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.
- mlspace_plugin-0.2.2/.gitignore +16 -0
- {mlspace_plugin-0.2.1 → mlspace_plugin-0.2.2}/INSTALL.md +15 -9
- {mlspace_plugin-0.2.1 → mlspace_plugin-0.2.2}/INSTALL_AGENT.md +6 -4
- {mlspace_plugin-0.2.1 → mlspace_plugin-0.2.2}/PKG-INFO +5 -39
- {mlspace_plugin-0.2.1 → mlspace_plugin-0.2.2}/README.md +3 -37
- {mlspace_plugin-0.2.1 → mlspace_plugin-0.2.2}/RELEASE.md +5 -5
- {mlspace_plugin-0.2.1 → mlspace_plugin-0.2.2}/plugin/.claude-plugin/plugin.json +1 -1
- {mlspace_plugin-0.2.1 → mlspace_plugin-0.2.2}/plugin/.codex-plugin/plugin.json +1 -1
- {mlspace_plugin-0.2.1 → mlspace_plugin-0.2.2}/plugin/.mcp.json +1 -1
- {mlspace_plugin-0.2.1 → mlspace_plugin-0.2.2}/plugin/README.md +8 -8
- {mlspace_plugin-0.2.1 → mlspace_plugin-0.2.2}/plugin/mcp.json +1 -1
- {mlspace_plugin-0.2.1 → mlspace_plugin-0.2.2}/plugin/plugin.json +1 -1
- {mlspace_plugin-0.2.1 → mlspace_plugin-0.2.2}/pyproject.toml +1 -1
- {mlspace_plugin-0.2.1 → mlspace_plugin-0.2.2}/scripts/serve_stdio.sh +0 -0
- {mlspace_plugin-0.2.1 → mlspace_plugin-0.2.2}/scripts/serve_stdio_dryrun.sh +0 -0
- {mlspace_plugin-0.2.1 → mlspace_plugin-0.2.2}/src/mlspace_mcp/__init__.py +1 -1
- {mlspace_plugin-0.2.1 → mlspace_plugin-0.2.2}/src/mlspace_mcp/client_setup.py +34 -0
- {mlspace_plugin-0.2.1 → mlspace_plugin-0.2.2}/src/mlspace_mcp/init_cli.py +27 -3
- {mlspace_plugin-0.2.1 → mlspace_plugin-0.2.2}/tests/test_client_setup.py +64 -1
- {mlspace_plugin-0.2.1 → mlspace_plugin-0.2.2}/tests/test_onboarding_init.py +64 -1
- mlspace_plugin-0.2.1/.gitignore +0 -53
- {mlspace_plugin-0.2.1 → mlspace_plugin-0.2.2}/.env.example +0 -0
- {mlspace_plugin-0.2.1 → mlspace_plugin-0.2.2}/AGENTS.md +0 -0
- {mlspace_plugin-0.2.1 → mlspace_plugin-0.2.2}/CONTRIBUTING.md +0 -0
- {mlspace_plugin-0.2.1 → mlspace_plugin-0.2.2}/LICENSE +0 -0
- {mlspace_plugin-0.2.1 → mlspace_plugin-0.2.2}/plugin/skills/mlspace-call-deployed-model-predict/SKILL.md +0 -0
- {mlspace_plugin-0.2.1 → mlspace_plugin-0.2.2}/plugin/skills/mlspace-deploy-inference-service/SKILL.md +0 -0
- {mlspace_plugin-0.2.1 → mlspace_plugin-0.2.2}/plugin/skills/mlspace-diagnose-data-transfer/SKILL.md +0 -0
- {mlspace_plugin-0.2.1 → mlspace_plugin-0.2.2}/plugin/skills/mlspace-diagnose-failed-training-job/SKILL.md +0 -0
- {mlspace_plugin-0.2.1 → mlspace_plugin-0.2.2}/plugin/skills/mlspace-diagnose-job-launch-rejection/SKILL.md +0 -0
- {mlspace_plugin-0.2.1 → mlspace_plugin-0.2.2}/plugin/skills/mlspace-diagnose-unhealthy-inference/SKILL.md +0 -0
- {mlspace_plugin-0.2.1 → mlspace_plugin-0.2.2}/plugin/skills/mlspace-discover-launchable-resources/SKILL.md +0 -0
- {mlspace_plugin-0.2.1 → mlspace_plugin-0.2.2}/plugin/skills/mlspace-launch-training-job/SKILL.md +0 -0
- {mlspace_plugin-0.2.1 → mlspace_plugin-0.2.2}/plugin/skills/mlspace-list-running-jobs/SKILL.md +0 -0
- {mlspace_plugin-0.2.1 → mlspace_plugin-0.2.2}/plugin/skills/mlspace-resume-notebook-and-set-autoshutdown/SKILL.md +0 -0
- {mlspace_plugin-0.2.1 → mlspace_plugin-0.2.2}/plugin/skills/mlspace-stage-s3-to-nfs/SKILL.md +0 -0
- {mlspace_plugin-0.2.1 → mlspace_plugin-0.2.2}/plugin/skills/mlspace-transfer-compute-node/SKILL.md +0 -0
- {mlspace_plugin-0.2.1 → mlspace_plugin-0.2.2}/scripts/build_plugin.py +0 -0
- {mlspace_plugin-0.2.1 → mlspace_plugin-0.2.2}/scripts/demo_multi_workspace.py +0 -0
- {mlspace_plugin-0.2.1 → mlspace_plugin-0.2.2}/scripts/gen_body_schemas.py +0 -0
- {mlspace_plugin-0.2.1 → mlspace_plugin-0.2.2}/scripts/gen_plugin.py +0 -0
- {mlspace_plugin-0.2.1 → mlspace_plugin-0.2.2}/scripts/mcp_call.py +0 -0
- {mlspace_plugin-0.2.1 → mlspace_plugin-0.2.2}/src/mlspace_mcp/__main__.py +0 -0
- {mlspace_plugin-0.2.1 → mlspace_plugin-0.2.2}/src/mlspace_mcp/_status.py +0 -0
- {mlspace_plugin-0.2.1 → mlspace_plugin-0.2.2}/src/mlspace_mcp/auth.py +0 -0
- {mlspace_plugin-0.2.1 → mlspace_plugin-0.2.2}/src/mlspace_mcp/bodyspec.py +0 -0
- {mlspace_plugin-0.2.1 → mlspace_plugin-0.2.2}/src/mlspace_mcp/client.py +0 -0
- {mlspace_plugin-0.2.1 → mlspace_plugin-0.2.2}/src/mlspace_mcp/config.py +0 -0
- {mlspace_plugin-0.2.1 → mlspace_plugin-0.2.2}/src/mlspace_mcp/errors.py +0 -0
- {mlspace_plugin-0.2.1 → mlspace_plugin-0.2.2}/src/mlspace_mcp/formatting.py +0 -0
- {mlspace_plugin-0.2.1 → mlspace_plugin-0.2.2}/src/mlspace_mcp/job_hooks.py +0 -0
- {mlspace_plugin-0.2.1 → mlspace_plugin-0.2.2}/src/mlspace_mcp/jsonc.py +0 -0
- {mlspace_plugin-0.2.1 → mlspace_plugin-0.2.2}/src/mlspace_mcp/overview.py +0 -0
- {mlspace_plugin-0.2.1 → mlspace_plugin-0.2.2}/src/mlspace_mcp/registry.py +0 -0
- {mlspace_plugin-0.2.1 → mlspace_plugin-0.2.2}/src/mlspace_mcp/resource_refs.py +0 -0
- {mlspace_plugin-0.2.1 → mlspace_plugin-0.2.2}/src/mlspace_mcp/server.py +0 -0
- {mlspace_plugin-0.2.1 → mlspace_plugin-0.2.2}/src/mlspace_mcp/setup_ui.py +0 -0
- {mlspace_plugin-0.2.1 → mlspace_plugin-0.2.2}/src/mlspace_mcp/spec/body_schemas.json +0 -0
- {mlspace_plugin-0.2.1 → mlspace_plugin-0.2.2}/src/mlspace_mcp/spec/openapi.json +0 -0
- {mlspace_plugin-0.2.1 → mlspace_plugin-0.2.2}/src/mlspace_mcp/tls.py +0 -0
- {mlspace_plugin-0.2.1 → mlspace_plugin-0.2.2}/src/mlspace_mcp/tools/__init__.py +0 -0
- {mlspace_plugin-0.2.1 → mlspace_plugin-0.2.2}/src/mlspace_mcp/tools/allocations.py +0 -0
- {mlspace_plugin-0.2.1 → mlspace_plugin-0.2.2}/src/mlspace_mcp/tools/async_inference.py +0 -0
- {mlspace_plugin-0.2.1 → mlspace_plugin-0.2.2}/src/mlspace_mcp/tools/build_image.py +0 -0
- {mlspace_plugin-0.2.1 → mlspace_plugin-0.2.2}/src/mlspace_mcp/tools/dalle.py +0 -0
- {mlspace_plugin-0.2.1 → mlspace_plugin-0.2.2}/src/mlspace_mcp/tools/data_transfer.py +0 -0
- {mlspace_plugin-0.2.1 → mlspace_plugin-0.2.2}/src/mlspace_mcp/tools/docker_registry.py +0 -0
- {mlspace_plugin-0.2.1 → mlspace_plugin-0.2.2}/src/mlspace_mcp/tools/inference.py +0 -0
- {mlspace_plugin-0.2.1 → mlspace_plugin-0.2.2}/src/mlspace_mcp/tools/jobs.py +0 -0
- {mlspace_plugin-0.2.1 → mlspace_plugin-0.2.2}/src/mlspace_mcp/tools/notebooks.py +0 -0
- {mlspace_plugin-0.2.1 → mlspace_plugin-0.2.2}/src/mlspace_mcp/tools/queues.py +0 -0
- {mlspace_plugin-0.2.1 → mlspace_plugin-0.2.2}/src/mlspace_mcp/tools/resources.py +0 -0
- {mlspace_plugin-0.2.1 → mlspace_plugin-0.2.2}/src/mlspace_mcp/tools/tensorboards.py +0 -0
- {mlspace_plugin-0.2.1 → mlspace_plugin-0.2.2}/src/mlspace_mcp/tools/workspaces.py +0 -0
- {mlspace_plugin-0.2.1 → mlspace_plugin-0.2.2}/src/mlspace_mcp/workspace_context.py +0 -0
- {mlspace_plugin-0.2.1 → mlspace_plugin-0.2.2}/tests/conftest.py +0 -0
- {mlspace_plugin-0.2.1 → mlspace_plugin-0.2.2}/tests/specutil.py +0 -0
- {mlspace_plugin-0.2.1 → mlspace_plugin-0.2.2}/tests/test_allocations.py +0 -0
- {mlspace_plugin-0.2.1 → mlspace_plugin-0.2.2}/tests/test_artifact_sync.py +0 -0
- {mlspace_plugin-0.2.1 → mlspace_plugin-0.2.2}/tests/test_async_inference.py +0 -0
- {mlspace_plugin-0.2.1 → mlspace_plugin-0.2.2}/tests/test_auth.py +0 -0
- {mlspace_plugin-0.2.1 → mlspace_plugin-0.2.2}/tests/test_autofill.py +0 -0
- {mlspace_plugin-0.2.1 → mlspace_plugin-0.2.2}/tests/test_bodyspec.py +0 -0
- {mlspace_plugin-0.2.1 → mlspace_plugin-0.2.2}/tests/test_build_image.py +0 -0
- {mlspace_plugin-0.2.1 → mlspace_plugin-0.2.2}/tests/test_client.py +0 -0
- {mlspace_plugin-0.2.1 → mlspace_plugin-0.2.2}/tests/test_configs_shrink.py +0 -0
- {mlspace_plugin-0.2.1 → mlspace_plugin-0.2.2}/tests/test_dalle.py +0 -0
- {mlspace_plugin-0.2.1 → mlspace_plugin-0.2.2}/tests/test_data_transfer.py +0 -0
- {mlspace_plugin-0.2.1 → mlspace_plugin-0.2.2}/tests/test_docker_registry.py +0 -0
- {mlspace_plugin-0.2.1 → mlspace_plugin-0.2.2}/tests/test_drift.py +0 -0
- {mlspace_plugin-0.2.1 → mlspace_plugin-0.2.2}/tests/test_dry_run.py +0 -0
- {mlspace_plugin-0.2.1 → mlspace_plugin-0.2.2}/tests/test_dry_run_fidelity.py +0 -0
- {mlspace_plugin-0.2.1 → mlspace_plugin-0.2.2}/tests/test_env_file.py +0 -0
- {mlspace_plugin-0.2.1 → mlspace_plugin-0.2.2}/tests/test_errors.py +0 -0
- {mlspace_plugin-0.2.1 → mlspace_plugin-0.2.2}/tests/test_false_success.py +0 -0
- {mlspace_plugin-0.2.1 → mlspace_plugin-0.2.2}/tests/test_formatting.py +0 -0
- {mlspace_plugin-0.2.1 → mlspace_plugin-0.2.2}/tests/test_init_cli.py +0 -0
- {mlspace_plugin-0.2.1 → mlspace_plugin-0.2.2}/tests/test_integration.py +0 -0
- {mlspace_plugin-0.2.1 → mlspace_plugin-0.2.2}/tests/test_job_status.py +0 -0
- {mlspace_plugin-0.2.1 → mlspace_plugin-0.2.2}/tests/test_jobs.py +0 -0
- {mlspace_plugin-0.2.1 → mlspace_plugin-0.2.2}/tests/test_log_normalisation.py +0 -0
- {mlspace_plugin-0.2.1 → mlspace_plugin-0.2.2}/tests/test_mcp_error_contract.py +0 -0
- {mlspace_plugin-0.2.1 → mlspace_plugin-0.2.2}/tests/test_notebooks.py +0 -0
- {mlspace_plugin-0.2.1 → mlspace_plugin-0.2.2}/tests/test_onboarding_tls.py +0 -0
- {mlspace_plugin-0.2.1 → mlspace_plugin-0.2.2}/tests/test_overview.py +0 -0
- {mlspace_plugin-0.2.1 → mlspace_plugin-0.2.2}/tests/test_plugin_bundle.py +0 -0
- {mlspace_plugin-0.2.1 → mlspace_plugin-0.2.2}/tests/test_plugin_cli.py +0 -0
- {mlspace_plugin-0.2.1 → mlspace_plugin-0.2.2}/tests/test_plugin_release.py +0 -0
- {mlspace_plugin-0.2.1 → mlspace_plugin-0.2.2}/tests/test_preflight.py +0 -0
- {mlspace_plugin-0.2.1 → mlspace_plugin-0.2.2}/tests/test_queue_scope.py +0 -0
- {mlspace_plugin-0.2.1 → mlspace_plugin-0.2.2}/tests/test_queues.py +0 -0
- {mlspace_plugin-0.2.1 → mlspace_plugin-0.2.2}/tests/test_registry.py +0 -0
- {mlspace_plugin-0.2.1 → mlspace_plugin-0.2.2}/tests/test_resource_refs.py +0 -0
- {mlspace_plugin-0.2.1 → mlspace_plugin-0.2.2}/tests/test_resources.py +0 -0
- {mlspace_plugin-0.2.1 → mlspace_plugin-0.2.2}/tests/test_route_confusion.py +0 -0
- {mlspace_plugin-0.2.1 → mlspace_plugin-0.2.2}/tests/test_setup_ui.py +0 -0
- {mlspace_plugin-0.2.1 → mlspace_plugin-0.2.2}/tests/test_skills.py +0 -0
- {mlspace_plugin-0.2.1 → mlspace_plugin-0.2.2}/tests/test_tensorboards.py +0 -0
- {mlspace_plugin-0.2.1 → mlspace_plugin-0.2.2}/tests/test_workspace_context.py +0 -0
- {mlspace_plugin-0.2.1 → mlspace_plugin-0.2.2}/tests/test_workspaces.py +0 -0
- {mlspace_plugin-0.2.1 → mlspace_plugin-0.2.2}/tests/test_wrapper_hardening.py +0 -0
|
@@ -20,8 +20,11 @@ mlspace-plugin setup
|
|
|
20
20
|
```
|
|
21
21
|
|
|
22
22
|
Если пакет уже установлен: `uv tool install --force .`.
|
|
23
|
-
При установке из wheel: `uv tool install /путь/к/mlspace_plugin-0.2.
|
|
23
|
+
При установке из wheel: `uv tool install /путь/к/mlspace_plugin-0.2.2-py3-none-any.whl`.
|
|
24
24
|
Не используйте editable-установку из `/tmp` для постоянной работы.
|
|
25
|
+
Далее команды показаны для PyPI. После установки исходников или wheel через
|
|
26
|
+
`uv tool install` заменяйте `uvx mlspace-plugin@latest` на `mlspace-plugin`,
|
|
27
|
+
чтобы использовать именно установленную сборку.
|
|
25
28
|
|
|
26
29
|
Мастер:
|
|
27
30
|
|
|
@@ -41,7 +44,7 @@ mlspace-plugin setup
|
|
|
41
44
|
## Stage и корпоративные сертификаты
|
|
42
45
|
|
|
43
46
|
```bash
|
|
44
|
-
mlspace-plugin setup --base-url https://mlspace-stage.example.com
|
|
47
|
+
uvx mlspace-plugin@latest setup --base-url https://mlspace-stage.example.com
|
|
45
48
|
```
|
|
46
49
|
|
|
47
50
|
Настройка и работа сервера используют системные доверенные сертификаты: Keychain
|
|
@@ -49,7 +52,7 @@ mlspace-plugin setup --base-url https://mlspace-stage.example.com
|
|
|
49
52
|
попросит путь к PEM, полученному у администратора. Можно указать заранее:
|
|
50
53
|
|
|
51
54
|
```bash
|
|
52
|
-
mlspace-plugin setup --ca-file /путь/к/corporate-ca.pem
|
|
55
|
+
uvx mlspace-plugin@latest setup --ca-file /путь/к/corporate-ca.pem
|
|
53
56
|
```
|
|
54
57
|
|
|
55
58
|
Мастер сохранит копию CA рядом с настройками. Переменные текущего терминала и
|
|
@@ -63,10 +66,10 @@ mlspace-plugin setup --ca-file /путь/к/corporate-ca.pem
|
|
|
63
66
|
## Повторная настройка
|
|
64
67
|
|
|
65
68
|
```bash
|
|
66
|
-
mlspace-plugin setup --force
|
|
67
|
-
mlspace-plugin setup --force --replace-credentials
|
|
68
|
-
mlspace-plugin setup --client opencode
|
|
69
|
-
mlspace-plugin setup --client-path opencode=/полный/путь/opencode
|
|
69
|
+
uvx mlspace-plugin@latest setup --force
|
|
70
|
+
uvx mlspace-plugin@latest setup --force --replace-credentials
|
|
71
|
+
uvx mlspace-plugin@latest setup --force --client opencode
|
|
72
|
+
uvx mlspace-plugin@latest setup --force --client-path opencode=/полный/путь/opencode
|
|
70
73
|
```
|
|
71
74
|
|
|
72
75
|
`--force` повторяет настройку с сохранёнными ключами на прежнем endpoint.
|
|
@@ -88,7 +91,7 @@ mlspace-plugin setup --client-path opencode=/полный/путь/opencode
|
|
|
88
91
|
|
|
89
92
|
## Linux по SSH / установка агентом
|
|
90
93
|
|
|
91
|
-
Запустите `mlspace-plugin setup` в своей SSH-сессии. Браузер и графическая среда не нужны.
|
|
94
|
+
Запустите `uvx mlspace-plugin@latest setup` в своей SSH-сессии. Браузер и графическая среда не нужны.
|
|
92
95
|
Если установкой занимается агент, он готовит пакет, а ключи вы вводите сами в своём
|
|
93
96
|
терминале. После завершения сообщите агенту «готово», без ключей и содержимого `.env`.
|
|
94
97
|
|
|
@@ -101,7 +104,10 @@ mlspace-plugin setup --client-path opencode=/полный/путь/opencode
|
|
|
101
104
|
|
|
102
105
|
Для установки через PyPI повторите `uvx mlspace-plugin@latest setup --force`.
|
|
103
106
|
Мастер сохранит ключи, предложит выбор workspace и обновит скиллы без локальных
|
|
104
|
-
изменений.
|
|
107
|
+
изменений. MCP обновляется во всех клиентах, ранее подключённых мастером,
|
|
108
|
+
чтобы версия сервера совпадала с общими скиллами. `--client` выбирает новые
|
|
109
|
+
подключения, а существующие управляемые подключения обновляются вместе.
|
|
110
|
+
Изменённые вручную регистрации и скиллы вызовут конфликт с указанием файла.
|
|
105
111
|
Регистрация MCP, созданная мастером и не изменённая вручную, обновляется
|
|
106
112
|
автоматически. Посторонние и изменённые регистрации требуют разбора конфликта.
|
|
107
113
|
Для постоянной установки: `uv tool upgrade mlspace-plugin`, затем
|
|
@@ -9,10 +9,12 @@
|
|
|
9
9
|
При установке из исходников установите пакет в постоянное окружение: `uv tool install .` из checkout или `uv tool install /path/package.whl`.
|
|
10
10
|
Для уже установленного пакета добавьте `--force` только в рамках запрошенного обновления.
|
|
11
11
|
Не оставляйте постоянный запуск на editable-исходниках в `/tmp`.
|
|
12
|
+
Далее команды показаны для PyPI. Если установили исходники/wheel через `uv tool install`,
|
|
13
|
+
заменяйте префикс `uvx mlspace-plugin@latest` на `mlspace-plugin`, чтобы использовать именно эту сборку.
|
|
12
14
|
2. Не просите Key ID/Secret в чате, не читайте `.env`, не печатайте окружение и
|
|
13
15
|
не вставляйте ключи в аргументы shell/CLI. Не запускайте интерактивный ввод
|
|
14
16
|
ключей в терминале, который видите только вы.
|
|
15
|
-
3. Дайте человеку одну команду `mlspace-plugin setup` для его собственного терминала
|
|
17
|
+
3. Дайте человеку одну команду `uvx mlspace-plugin@latest setup` для его собственного терминала
|
|
16
18
|
(по SSH — на целевой машине). Для stage добавьте согласованный `--base-url`.
|
|
17
19
|
Объясните: два ключа под звёздочками → поиск и выбор workspace → подключение.
|
|
18
20
|
Дождитесь сообщения о завершении. Человек не должен присылать секреты или файл.
|
|
@@ -21,7 +23,7 @@
|
|
|
21
23
|
клиента можно заранее дать `--client claude-code`, `--client codex` или `--client opencode`.
|
|
22
24
|
Сбой TLS исправляется системным CA или PEM от администратора через `--ca-file`.
|
|
23
25
|
Никогда не отключайте TLS и не подставляйте сертификат с недоверенного сервера.
|
|
24
|
-
5. Проверьте `mlspace-plugin --list-tools`. Эта команда проверяет только состав
|
|
26
|
+
5. Проверьте `uvx mlspace-plugin@latest --list-tools`. Эта команда проверяет только состав
|
|
25
27
|
инструментов, а не авторизацию и не загрузку выбранным клиентом.
|
|
26
28
|
Попросите открыть новую сессию клиента (при необходимости перезапустить).
|
|
27
29
|
После появления MCP выполните read-only запрос списка выбранных workspace.
|
|
@@ -36,7 +38,7 @@
|
|
|
36
38
|
файл, передавайте **путь**, не читайте содержимое. Для поиска нужных workspace:
|
|
37
39
|
|
|
38
40
|
```bash
|
|
39
|
-
mlspace-plugin setup --non-interactive --credentials-file /path/credentials.env \
|
|
41
|
+
uvx mlspace-plugin@latest setup --non-interactive --credentials-file /path/credentials.env \
|
|
40
42
|
--base-url https://api.ai.cloud.ru --list-workspaces --search project --limit 20
|
|
41
43
|
```
|
|
42
44
|
|
|
@@ -45,7 +47,7 @@ mlspace-plugin setup --non-interactive --credentials-file /path/credentials.env
|
|
|
45
47
|
Установка с точными ID, без интерактивных вопросов:
|
|
46
48
|
|
|
47
49
|
```bash
|
|
48
|
-
mlspace-plugin setup --non-interactive --credentials-file /path/credentials.env \
|
|
50
|
+
uvx mlspace-plugin@latest setup --non-interactive --credentials-file /path/credentials.env \
|
|
49
51
|
--client opencode --workspace WORKSPACE_UUID
|
|
50
52
|
```
|
|
51
53
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
2
|
Name: mlspace-plugin
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.2
|
|
4
4
|
Summary: Cloud.ru MLSpace plugin: MCP server, agent skills and setup
|
|
5
5
|
Project-URL: Repository, https://github.com/noesskeetit/mlspace-plugin
|
|
6
6
|
Project-URL: Issues, https://github.com/noesskeetit/mlspace-plugin/issues
|
|
@@ -114,7 +114,7 @@ TLS использует системные доверенные CA. Для ко
|
|
|
114
114
|
мастер сохраняет копию для последующих запусков. Ошибка TLS/сети останавливает
|
|
115
115
|
настройку без перезаписи прежнего файла и без запроса ручного x-api-key.
|
|
116
116
|
|
|
117
|
-
Повторная настройка — `mlspace-plugin setup --force`: сохранённые ключи на прежнем
|
|
117
|
+
Повторная настройка — `uvx mlspace-plugin@latest setup --force`: сохранённые ключи на прежнем
|
|
118
118
|
endpoint переиспользуются. Для замены ключей добавьте `--replace-credentials`.
|
|
119
119
|
Для stage укажите `--base-url https://mlspace-stage.example.com`.
|
|
120
120
|
Только credentials без подключения клиентов — `--config-only`.
|
|
@@ -274,11 +274,11 @@ write-запросов подаются параметром `body: dict`; об
|
|
|
274
274
|
## Плагин: Codex, Claude Code и совместимые агенты
|
|
275
275
|
|
|
276
276
|
[Поставка MLSpace](plugin/README.md) объединяет MCP-сервер и 12 скиллов.
|
|
277
|
-
|
|
277
|
+
Архив релиза `mlspace-plugin-<версия>.zip` содержит точную версию сервера,
|
|
278
278
|
общие скиллы и манифесты для загрузчиков Codex и Claude Code. Его установка
|
|
279
279
|
не зависит от публикации `mlspace-plugin` в публичном PyPI.
|
|
280
280
|
|
|
281
|
-
После распаковки и однократного `
|
|
281
|
+
После распаковки и однократного `setup --config-only` через wheel из архива добавьте каталог релиза как
|
|
282
282
|
marketplace клиента. [Команды установки и сборки](plugin/README.md).
|
|
283
283
|
Ключи остаются в пользовательском конфиге вне плагина.
|
|
284
284
|
|
|
@@ -318,40 +318,6 @@ MCP-подключение; поддержку конкретного загру
|
|
|
318
318
|
|
|
319
319
|
---
|
|
320
320
|
|
|
321
|
-
## Защиты от дефектов API
|
|
322
|
-
|
|
323
|
-
Обёртка гасит несколько особенностей MLSpace API, на которых агент иначе спотыкается.
|
|
324
|
-
API-ловушки ниже проверялись на живом API: основная часть — 03.08.2026,
|
|
325
|
-
региональность `/home/jovyan` — 12.08.2026. Допустимость binary-команд подтверждена
|
|
326
|
-
[официальной CLI-документацией](https://cloud.ru/docs/aicloud/mlspace-cli/topics/references__job-submit)
|
|
327
|
-
и локальной схемой; проверка доступности команды в образе остаётся отдельным шагом.
|
|
328
|
-
|
|
329
|
-
- **Тип entrypoint и регион проверяются до отправки.** Явный `region` обязателен для
|
|
330
|
-
каждого типа задачи. Для `type=binary` поле `script` — непустая команда или путь к
|
|
331
|
-
исполняемому файлу (`ls -lah`, `python /home/jovyan/launch.py -f script.py`); MCP
|
|
332
|
-
передаёт строку без изменений и не заявляет, что команда существует в выбранном
|
|
333
|
-
образе. Для остальных типов сохраняется проверка абсолютного пути под
|
|
334
|
-
`/home/jovyan`. Это NFS конкретного региона: одинаковый путь в notebook `SR006` и
|
|
335
|
-
job `A100-MT` может указывать на разные данные. Public API не умеет сделать `stat`
|
|
336
|
-
на целевом NFS, поэтому наличие файла подтверждается same-region notebook'ом или
|
|
337
|
-
transfer'ом. Локальная проверка формы пути или dry-run не проверяет наличие файла.
|
|
338
|
-
- **Порядок лога восстанавливается.** `logs?tail=N` возвращает строки в обратном
|
|
339
|
-
порядке — трейсбэк переворачивается, и причина оказывается ниже следствия. MCP
|
|
340
|
-
сортирует обратно и помечает это в ответе, чтобы модель знала, что порядку можно
|
|
341
|
-
доверять. Без `tail` порядок правильный и не трогается.
|
|
342
|
-
- **Состояния вместо прозы.** Строки `"No such job"` (логи протухли по retention) и
|
|
343
|
-
`Job … in queue. Try later` (задача в очереди) приходят внутри `HTTP 200`. MCP
|
|
344
|
-
распознаёт их и выдаёт понятную типизированную пометку, чтобы «логи устарели» не
|
|
345
|
-
читалось как «задачи не существует».
|
|
346
|
-
- **Ретрай не уходит в бесконечность.** Часть ручек отдаёт `500` детерминированно
|
|
347
|
-
(например, assignments на 6 из 7 аллокаций). Совет при `5xx` — повторить один раз,
|
|
348
|
-
дальше считать отказ постоянным.
|
|
349
|
-
- **Каталог образов сжимается.** `configs` отдаёт ~200 КБ (≈51k токенов), дублируя
|
|
350
|
-
образы под каждым `instance_type`; MCP дедуплицирует их без потери данных и умеет
|
|
351
|
-
отфильтровать один регион.
|
|
352
|
-
|
|
353
|
-
---
|
|
354
|
-
|
|
355
321
|
## Разработка
|
|
356
322
|
|
|
357
323
|
```bash
|
|
@@ -63,7 +63,7 @@ TLS использует системные доверенные CA. Для ко
|
|
|
63
63
|
мастер сохраняет копию для последующих запусков. Ошибка TLS/сети останавливает
|
|
64
64
|
настройку без перезаписи прежнего файла и без запроса ручного x-api-key.
|
|
65
65
|
|
|
66
|
-
Повторная настройка — `mlspace-plugin setup --force`: сохранённые ключи на прежнем
|
|
66
|
+
Повторная настройка — `uvx mlspace-plugin@latest setup --force`: сохранённые ключи на прежнем
|
|
67
67
|
endpoint переиспользуются. Для замены ключей добавьте `--replace-credentials`.
|
|
68
68
|
Для stage укажите `--base-url https://mlspace-stage.example.com`.
|
|
69
69
|
Только credentials без подключения клиентов — `--config-only`.
|
|
@@ -223,11 +223,11 @@ write-запросов подаются параметром `body: dict`; об
|
|
|
223
223
|
## Плагин: Codex, Claude Code и совместимые агенты
|
|
224
224
|
|
|
225
225
|
[Поставка MLSpace](plugin/README.md) объединяет MCP-сервер и 12 скиллов.
|
|
226
|
-
|
|
226
|
+
Архив релиза `mlspace-plugin-<версия>.zip` содержит точную версию сервера,
|
|
227
227
|
общие скиллы и манифесты для загрузчиков Codex и Claude Code. Его установка
|
|
228
228
|
не зависит от публикации `mlspace-plugin` в публичном PyPI.
|
|
229
229
|
|
|
230
|
-
После распаковки и однократного `
|
|
230
|
+
После распаковки и однократного `setup --config-only` через wheel из архива добавьте каталог релиза как
|
|
231
231
|
marketplace клиента. [Команды установки и сборки](plugin/README.md).
|
|
232
232
|
Ключи остаются в пользовательском конфиге вне плагина.
|
|
233
233
|
|
|
@@ -267,40 +267,6 @@ MCP-подключение; поддержку конкретного загру
|
|
|
267
267
|
|
|
268
268
|
---
|
|
269
269
|
|
|
270
|
-
## Защиты от дефектов API
|
|
271
|
-
|
|
272
|
-
Обёртка гасит несколько особенностей MLSpace API, на которых агент иначе спотыкается.
|
|
273
|
-
API-ловушки ниже проверялись на живом API: основная часть — 03.08.2026,
|
|
274
|
-
региональность `/home/jovyan` — 12.08.2026. Допустимость binary-команд подтверждена
|
|
275
|
-
[официальной CLI-документацией](https://cloud.ru/docs/aicloud/mlspace-cli/topics/references__job-submit)
|
|
276
|
-
и локальной схемой; проверка доступности команды в образе остаётся отдельным шагом.
|
|
277
|
-
|
|
278
|
-
- **Тип entrypoint и регион проверяются до отправки.** Явный `region` обязателен для
|
|
279
|
-
каждого типа задачи. Для `type=binary` поле `script` — непустая команда или путь к
|
|
280
|
-
исполняемому файлу (`ls -lah`, `python /home/jovyan/launch.py -f script.py`); MCP
|
|
281
|
-
передаёт строку без изменений и не заявляет, что команда существует в выбранном
|
|
282
|
-
образе. Для остальных типов сохраняется проверка абсолютного пути под
|
|
283
|
-
`/home/jovyan`. Это NFS конкретного региона: одинаковый путь в notebook `SR006` и
|
|
284
|
-
job `A100-MT` может указывать на разные данные. Public API не умеет сделать `stat`
|
|
285
|
-
на целевом NFS, поэтому наличие файла подтверждается same-region notebook'ом или
|
|
286
|
-
transfer'ом. Локальная проверка формы пути или dry-run не проверяет наличие файла.
|
|
287
|
-
- **Порядок лога восстанавливается.** `logs?tail=N` возвращает строки в обратном
|
|
288
|
-
порядке — трейсбэк переворачивается, и причина оказывается ниже следствия. MCP
|
|
289
|
-
сортирует обратно и помечает это в ответе, чтобы модель знала, что порядку можно
|
|
290
|
-
доверять. Без `tail` порядок правильный и не трогается.
|
|
291
|
-
- **Состояния вместо прозы.** Строки `"No such job"` (логи протухли по retention) и
|
|
292
|
-
`Job … in queue. Try later` (задача в очереди) приходят внутри `HTTP 200`. MCP
|
|
293
|
-
распознаёт их и выдаёт понятную типизированную пометку, чтобы «логи устарели» не
|
|
294
|
-
читалось как «задачи не существует».
|
|
295
|
-
- **Ретрай не уходит в бесконечность.** Часть ручек отдаёт `500` детерминированно
|
|
296
|
-
(например, assignments на 6 из 7 аллокаций). Совет при `5xx` — повторить один раз,
|
|
297
|
-
дальше считать отказ постоянным.
|
|
298
|
-
- **Каталог образов сжимается.** `configs` отдаёт ~200 КБ (≈51k токенов), дублируя
|
|
299
|
-
образы под каждым `instance_type`; MCP дедуплицирует их без потери данных и умеет
|
|
300
|
-
отфильтровать один регион.
|
|
301
|
-
|
|
302
|
-
---
|
|
303
|
-
|
|
304
270
|
## Разработка
|
|
305
271
|
|
|
306
272
|
```bash
|
|
@@ -7,11 +7,11 @@
|
|
|
7
7
|
`uv lock` и `uv run python scripts/gen_plugin.py`. Обновите примеры имён wheel/ZIP
|
|
8
8
|
в инструкциях, если версия присутствует в имени файла.
|
|
9
9
|
2. Выполните проверки из `CONTRIBUTING.md` на Linux и macOS.
|
|
10
|
-
3. Соберите пакет (в командах ниже замените 0.2.
|
|
10
|
+
3. Соберите пакет (в командах ниже замените 0.2.2 на выпускаемую версию):
|
|
11
11
|
|
|
12
12
|
```sh
|
|
13
|
-
uv build --out-dir dist/release-0.2.
|
|
14
|
-
uv run python scripts/build_plugin.py --wheel dist/release-0.2.
|
|
13
|
+
uv build --out-dir dist/release-0.2.2
|
|
14
|
+
uv run python scripts/build_plugin.py --wheel dist/release-0.2.2/mlspace_plugin-0.2.2-py3-none-any.whl --output dist/plugin-release-0.2.2
|
|
15
15
|
```
|
|
16
16
|
|
|
17
17
|
4. Просмотрите содержимое wheel, sdist и ZIP. В них не должно быть credentials,
|
|
@@ -23,11 +23,11 @@ uv run python scripts/build_plugin.py --wheel dist/release-0.2.1/mlspace_plugin-
|
|
|
23
23
|
`UV_PUBLISH_TOKEN` средствами вашего хранилища секретов, не аргументом команды:
|
|
24
24
|
|
|
25
25
|
```sh
|
|
26
|
-
uv publish --trusted-publishing never dist/release-0.2.
|
|
26
|
+
uv publish --trusted-publishing never dist/release-0.2.2/mlspace_plugin-0.2.2-py3-none-any.whl dist/release-0.2.2/mlspace_plugin-0.2.2.tar.gz
|
|
27
27
|
```
|
|
28
28
|
|
|
29
29
|
6. Проверьте версию и SHA256 обоих файлов через PyPI JSON API; проверьте установку
|
|
30
|
-
`uvx --from mlspace-plugin==0.2.
|
|
30
|
+
`uvx --from mlspace-plugin==0.2.2 mlspace-plugin --version` из нового кеша.
|
|
31
31
|
ZIP и SHA256SUMS приложите к тому же релизу публичного Git-репозитория.
|
|
32
32
|
|
|
33
33
|
Пакет не проверяет обновления в фоне. Для PyPI пользователь повторяет
|
|
@@ -7,9 +7,9 @@
|
|
|
7
7
|
|
|
8
8
|
## Установка из архива релиза
|
|
9
9
|
|
|
10
|
-
Скачайте `mlspace-plugin-0.2.
|
|
10
|
+
Скачайте `mlspace-plugin-0.2.2.zip` из опубликованного релиза проекта.
|
|
11
11
|
Распакуйте его в постоянный каталог, например `~/plugins`, и перейдите в
|
|
12
|
-
`mlspace-plugin-0.2.
|
|
12
|
+
`mlspace-plugin-0.2.2`. Сохраняйте каталог: локальный marketplace нужен для обновлений.
|
|
13
13
|
|
|
14
14
|
Требования: `uv` (`uvx`) в PATH, Python 3.10+ и установленный клиент агента.
|
|
15
15
|
При первом запуске `uv` устанавливает зависимости из настроенного Python index;
|
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
Настройте ключи сервисной учётной записи и выберите доступные workspace:
|
|
20
20
|
|
|
21
21
|
```sh
|
|
22
|
-
uvx --from ./plugins/mlspace/server/mlspace_plugin-0.2.
|
|
22
|
+
uvx --from ./plugins/mlspace/server/mlspace_plugin-0.2.2-py3-none-any.whl mlspace-plugin setup --config-only
|
|
23
23
|
```
|
|
24
24
|
|
|
25
25
|
Ключи вводятся в терминале. `setup` сохраняет их вне плагина в пользовательском
|
|
@@ -62,7 +62,7 @@ claude plugin install mlspace@mlspace
|
|
|
62
62
|
Для MCP-only клиента задайте команду `uvx` и аргументы:
|
|
63
63
|
|
|
64
64
|
```json
|
|
65
|
-
["--from", "/absolute/path/plugins/mlspace/server/mlspace_plugin-0.2.
|
|
65
|
+
["--from", "/absolute/path/plugins/mlspace/server/mlspace_plugin-0.2.2-py3-none-any.whl", "mlspace-plugin", "--transport", "stdio"]
|
|
66
66
|
```
|
|
67
67
|
|
|
68
68
|
`SKILL.md` можно подключать отдельно в клиентах с поддержкой Agent Skills.
|
|
@@ -71,7 +71,7 @@ claude plugin install mlspace@mlspace
|
|
|
71
71
|
## Состав архива
|
|
72
72
|
|
|
73
73
|
```text
|
|
74
|
-
mlspace-plugin-0.2.
|
|
74
|
+
mlspace-plugin-0.2.2/
|
|
75
75
|
├── .agents/plugins/marketplace.json
|
|
76
76
|
├── .claude-plugin/marketplace.json
|
|
77
77
|
├── README.md
|
|
@@ -82,7 +82,7 @@ mlspace-plugin-0.2.1/
|
|
|
82
82
|
├── .codex-plugin/plugin.json
|
|
83
83
|
├── .claude-plugin/plugin.json
|
|
84
84
|
├── .mcp.json
|
|
85
|
-
├── server/mlspace_plugin-0.2.
|
|
85
|
+
├── server/mlspace_plugin-0.2.2-py3-none-any.whl
|
|
86
86
|
└── skills/<12 скиллов>/SKILL.md
|
|
87
87
|
```
|
|
88
88
|
|
|
@@ -103,8 +103,8 @@ Portable MCP допускает отдельный `MLSPACE_ENV_FILE=${PLUGIN_DA
|
|
|
103
103
|
|
|
104
104
|
```sh
|
|
105
105
|
python scripts/gen_plugin.py --check
|
|
106
|
-
uv build --out-dir dist/release-0.2.
|
|
107
|
-
python scripts/build_plugin.py --wheel dist/release-0.2.
|
|
106
|
+
uv build --out-dir dist/release-0.2.2
|
|
107
|
+
python scripts/build_plugin.py --wheel dist/release-0.2.2/mlspace_plugin-0.2.2-py3-none-any.whl
|
|
108
108
|
```
|
|
109
109
|
|
|
110
110
|
Готовая поставка и ZIP появятся в `dist/plugins/`.
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"$schema": "https://agent-plugins.org/schemas/1.0.0/plugin.schema.json",
|
|
3
3
|
"name": "mlspace",
|
|
4
|
-
"version": "0.2.
|
|
4
|
+
"version": "0.2.2",
|
|
5
5
|
"description": "Cloud.ru MLSpace: training jobs, inference, notebooks, data transfer, registry and compute catalog — an MCP server plus skills.",
|
|
6
6
|
"author": {
|
|
7
7
|
"name": "Cloud.ru"
|
|
File without changes
|
|
File without changes
|
|
@@ -106,10 +106,41 @@ def _read_config(path: Path) -> dict[str, Any]:
|
|
|
106
106
|
return load_jsonc(path.read_text())
|
|
107
107
|
|
|
108
108
|
|
|
109
|
+
def managed_clients(overrides: dict[str, str] | None = None) -> dict[str, str]:
|
|
110
|
+
"""Find surviving managed registrations so one product updates together."""
|
|
111
|
+
state_path = user_config_file().parent / 'install-state.json'
|
|
112
|
+
state = json.loads(state_path.read_text()) if state_path.exists() else {}
|
|
113
|
+
detected = detect_clients()
|
|
114
|
+
result = {}
|
|
115
|
+
for client in state.get('clients', {}):
|
|
116
|
+
if client not in CLIENTS:
|
|
117
|
+
continue
|
|
118
|
+
config = _config(client)
|
|
119
|
+
previous_path = Path(state.get('configs', {}).get(client, str(config)))
|
|
120
|
+
key = {'claude-code': 'mcpServers', 'codex': 'mcp_servers', 'opencode': 'mcp'}[client]
|
|
121
|
+
if 'mlspace' not in _read_config(previous_path).get(key, {}):
|
|
122
|
+
continue # The user removed this connection; never recreate it.
|
|
123
|
+
if previous_path.absolute() != config.absolute():
|
|
124
|
+
variable = {'claude-code': 'CLAUDE_CONFIG_DIR', 'codex': 'CODEX_HOME',
|
|
125
|
+
'opencode': 'OPENCODE_CONFIG'}[client]
|
|
126
|
+
raise ValueError(f'Previously connected {client} uses {previous_path}. '
|
|
127
|
+
f'Restore its {variable} setting before updating all MLSpace clients.')
|
|
128
|
+
candidates = [(overrides or {}).get(client), state.get('executables', {}).get(client),
|
|
129
|
+
detected.get(client)]
|
|
130
|
+
executable = next((path for path in candidates if path and Path(path).is_file()
|
|
131
|
+
and os.access(path, os.X_OK)), None)
|
|
132
|
+
if not executable and client != 'opencode':
|
|
133
|
+
raise ValueError(f'Previously connected {client} executable is unavailable. Restore it or use '
|
|
134
|
+
f'--client-path {client}=/absolute/path before updating MLSpace.')
|
|
135
|
+
result[client] = executable or CLIENTS[client] # OpenCode config is written directly.
|
|
136
|
+
return result
|
|
137
|
+
|
|
138
|
+
|
|
109
139
|
def prepare_clients(clients: dict[str, str], env_file: Path, command: list[str]) -> dict[str, Any]:
|
|
110
140
|
"""Plan all writes and reject conflicts before setup saves credentials."""
|
|
111
141
|
state_path = user_config_file().parent / 'install-state.json'
|
|
112
142
|
state = json.loads(state_path.read_text()) if state_path.exists() else {}
|
|
143
|
+
clients = managed_clients(clients) | clients
|
|
113
144
|
sources = _skill_sources()
|
|
114
145
|
writes: dict[Path, str] = {}
|
|
115
146
|
registrations = []
|
|
@@ -201,6 +232,7 @@ def install_clients(clients: dict[str, str], env_file: Path, command: list[str])
|
|
|
201
232
|
subprocess.run(remove, env=clean_env, check=True,
|
|
202
233
|
capture_output=True, timeout=30)
|
|
203
234
|
if comparable != entry:
|
|
235
|
+
config.parent.mkdir(parents=True, exist_ok=True, mode=0o700)
|
|
204
236
|
arguments = ([executable, 'mcp', 'add', 'mlspace', '--scope', 'user', '--transport', 'stdio',
|
|
205
237
|
'--env', 'MLSPACE_ENV_FILE=' + str(env_file.absolute())]
|
|
206
238
|
if client == 'claude-code' else
|
|
@@ -216,6 +248,8 @@ def install_clients(clients: dict[str, str], env_file: Path, command: list[str])
|
|
|
216
248
|
if comparable_saved != entry:
|
|
217
249
|
raise ValueError(f'Client registration could not be confirmed for {client}; retry setup.')
|
|
218
250
|
state.setdefault('clients', {})[client] = _hash(json.dumps(saved, sort_keys=True))
|
|
251
|
+
state.setdefault('configs', {})[client] = str(config.absolute())
|
|
252
|
+
state.setdefault('executables', {})[client] = executable
|
|
219
253
|
atomic_write(state_path, json.dumps(state, indent=2))
|
|
220
254
|
installed.append(client)
|
|
221
255
|
except (subprocess.SubprocessError, OSError) as exc:
|
|
@@ -20,6 +20,7 @@ from .client_setup import (
|
|
|
20
20
|
atomic_write,
|
|
21
21
|
detect_clients,
|
|
22
22
|
install_clients,
|
|
23
|
+
managed_clients,
|
|
23
24
|
prepare_clients,
|
|
24
25
|
server_command,
|
|
25
26
|
)
|
|
@@ -166,16 +167,26 @@ def _verify(values: dict[str, str], *, base_url: str) -> str | None:
|
|
|
166
167
|
|
|
167
168
|
def _clients(requested: list[str], paths: list[str], non_interactive: bool) -> dict[str, str]:
|
|
168
169
|
detected = detect_clients()
|
|
170
|
+
explicit = {}
|
|
169
171
|
for item in paths:
|
|
170
172
|
name, separator, path = item.partition('=')
|
|
171
173
|
executable = Path(path).expanduser().absolute()
|
|
172
174
|
if not separator or name not in CLIENTS or not executable.is_file() or not os.access(executable, os.X_OK):
|
|
173
175
|
raise ValueError('Use --client-path CLIENT=/absolute/path/to/executable.')
|
|
174
176
|
detected[name] = str(executable)
|
|
177
|
+
explicit[name] = str(executable)
|
|
178
|
+
managed = managed_clients(explicit)
|
|
179
|
+
detected = managed | detected
|
|
175
180
|
if requested:
|
|
176
181
|
if any(name not in detected for name in requested):
|
|
177
182
|
raise ValueError('Requested client not found. Install it or use --client-path CLIENT=/path.')
|
|
178
183
|
return {name: detected[name] for name in requested}
|
|
184
|
+
if managed:
|
|
185
|
+
print('Updating all previously connected MLSpace clients: ' + ', '.join(managed), file=sys.stderr)
|
|
186
|
+
return managed | explicit
|
|
187
|
+
if not explicit and (user_config_file().parent / 'install-state.json').exists():
|
|
188
|
+
raise ValueError('No previous MLSpace connections remain. Select a new connection explicitly '
|
|
189
|
+
'with --client or --client-path.')
|
|
179
190
|
if len(detected) == 1:
|
|
180
191
|
print('Client detected: ' + next(iter(detected)), file=sys.stderr)
|
|
181
192
|
return detected
|
|
@@ -216,7 +227,7 @@ def run_init(path: Path | None = None, *, force: bool = False, verify: bool = Tr
|
|
|
216
227
|
print('\nSetup cancelled; previous credentials preserved unless saving already completed.', file=sys.stderr)
|
|
217
228
|
return 130
|
|
218
229
|
except EOFError:
|
|
219
|
-
print('\nNo interactive input. Run `mlspace-plugin setup` yourself in a terminal (SSH works). '
|
|
230
|
+
print('\nNo interactive input. Run `uvx mlspace-plugin@latest setup` yourself in a terminal (SSH works). '
|
|
220
231
|
'Agents: use INSTALL_AGENT.md; never ask for keys in chat.', file=sys.stderr)
|
|
221
232
|
return 1
|
|
222
233
|
except (ValueError, OSError, httpx.HTTPError) as exc:
|
|
@@ -234,6 +245,10 @@ def _run_init(path: Path | None, **options: Any) -> int:
|
|
|
234
245
|
raise EOFError
|
|
235
246
|
if options['from_env'] and options['credentials_file']:
|
|
236
247
|
raise ValueError('Choose either --from-env or --credentials-file.')
|
|
248
|
+
if target.is_symlink():
|
|
249
|
+
raise ValueError('Credentials target must not be a symlink.')
|
|
250
|
+
if target.exists() and not target.is_file():
|
|
251
|
+
raise ValueError('Credentials target must be a regular file.')
|
|
237
252
|
if target.exists() and not options['force'] and not listing:
|
|
238
253
|
raise ValueError('Configuration already exists. Use --force to reconfigure; saved keys will be reused.')
|
|
239
254
|
if not options['verify'] and (not options['config_only'] or not options['workspace_ids']):
|
|
@@ -252,7 +267,9 @@ def _run_init(path: Path | None, **options: Any) -> int:
|
|
|
252
267
|
options['clients'], options['client_paths'], non_interactive)
|
|
253
268
|
command = server_command() if selected_clients else []
|
|
254
269
|
if selected_clients:
|
|
255
|
-
prepare_clients(selected_clients, target, command)
|
|
270
|
+
plan = prepare_clients(selected_clients, target, command)
|
|
271
|
+
print('MCP and skills will be kept together for: ' +
|
|
272
|
+
', '.join(row[0] for row in plan['registrations']), file=sys.stderr)
|
|
256
273
|
if not listing:
|
|
257
274
|
print('[1/4] Access keys. Paste values only; asterisks hide both fields. '
|
|
258
275
|
'Keys stay in your user config, not in chat.', file=sys.stderr)
|
|
@@ -340,7 +357,14 @@ def _run_init(path: Path | None, **options: Any) -> int:
|
|
|
340
357
|
if selected_clients:
|
|
341
358
|
print('[4/4] Connecting MCP and installing skills…', file=sys.stderr)
|
|
342
359
|
installed = install_clients(selected_clients, target, command)
|
|
343
|
-
|
|
360
|
+
try:
|
|
361
|
+
_write(target, values)
|
|
362
|
+
except (OSError, ValueError) as exc:
|
|
363
|
+
if installed:
|
|
364
|
+
raise ValueError(f'MCP and skills installed: {", ".join(installed)}, but credentials could not be saved '
|
|
365
|
+
f'to {target}. Previous credentials were not replaced. Check this path and its '
|
|
366
|
+
'permissions, then rerun setup --force before using the MCP.') from exc
|
|
367
|
+
raise
|
|
344
368
|
print(f'Credentials saved: {target} (0600).', file=sys.stderr)
|
|
345
369
|
if not options['verify']:
|
|
346
370
|
print('UNVERIFIED configuration saved by explicit request. No client was connected.', file=sys.stderr)
|
|
@@ -103,7 +103,6 @@ def test_installed_native_cli_registration_in_isolated_home(tmp_path, monkeypatc
|
|
|
103
103
|
monkeypatch.setenv('XDG_CONFIG_HOME', str(tmp_path / '.config'))
|
|
104
104
|
monkeypatch.setenv('CLAUDE_CONFIG_DIR', str(tmp_path / '.claude'))
|
|
105
105
|
monkeypatch.setenv('CODEX_HOME', str(tmp_path / '.codex'))
|
|
106
|
-
(tmp_path / '.codex').mkdir()
|
|
107
106
|
args = ({client: executable}, tmp_path / 'fake.env', ['/bin/echo', '--transport', 'stdio'])
|
|
108
107
|
install_clients(*args)
|
|
109
108
|
data = _read_config(_config(client))
|
|
@@ -231,3 +230,67 @@ def test_local_wheel_in_uvx_cache_is_not_substituted_with_pypi(tmp_path, monkeyp
|
|
|
231
230
|
monkeypatch.setattr('shutil.which', lambda name: '/opt/bin/uvx' if name == 'uvx' else None)
|
|
232
231
|
with pytest.raises(ValueError, match='local wheel'):
|
|
233
232
|
client_setup.server_command()
|
|
233
|
+
|
|
234
|
+
|
|
235
|
+
def test_shared_skill_update_includes_previously_connected_clients(tmp_path, monkeypatch):
|
|
236
|
+
from mlspace_mcp import client_setup
|
|
237
|
+
monkeypatch.setenv('HOME', str(tmp_path))
|
|
238
|
+
monkeypatch.setenv('XDG_CONFIG_HOME', str(tmp_path / '.config'))
|
|
239
|
+
monkeypatch.setenv('CODEX_HOME', str(tmp_path / '.codex'))
|
|
240
|
+
monkeypatch.setattr(client_setup, '_skill_sources', lambda: {'mlspace-example': 'version one'})
|
|
241
|
+
client_setup.install_clients({'opencode': '/bin/true'}, tmp_path / '.env', ['runtime-v1'])
|
|
242
|
+
monkeypatch.setattr(client_setup, '_skill_sources', lambda: {'mlspace-example': 'version two'})
|
|
243
|
+
plan = client_setup.prepare_clients({'codex': '/bin/true'}, tmp_path / '.env', ['runtime-v2'])
|
|
244
|
+
registrations = {row[0]: row[3] for row in plan['registrations']}
|
|
245
|
+
assert registrations['opencode']['command'] == ['runtime-v2']
|
|
246
|
+
assert registrations['codex']['command'] == 'runtime-v2'
|
|
247
|
+
assert plan['writes'][tmp_path / '.agents/skills/mlspace-example/SKILL.md'] == 'version two'
|
|
248
|
+
|
|
249
|
+
|
|
250
|
+
def test_update_does_not_recreate_removed_client_registration(tmp_path, monkeypatch):
|
|
251
|
+
from mlspace_mcp import client_setup
|
|
252
|
+
monkeypatch.setenv('HOME', str(tmp_path))
|
|
253
|
+
monkeypatch.setenv('XDG_CONFIG_HOME', str(tmp_path / '.config'))
|
|
254
|
+
monkeypatch.setenv('CODEX_HOME', str(tmp_path / '.codex'))
|
|
255
|
+
client_setup.install_clients({'opencode': '/bin/true'}, tmp_path / '.env', ['runtime-v1'])
|
|
256
|
+
(tmp_path / '.config/opencode/opencode.json').write_text('{}')
|
|
257
|
+
plan = client_setup.prepare_clients({'codex': '/bin/true'}, tmp_path / '.env', ['runtime-v2'])
|
|
258
|
+
assert [row[0] for row in plan['registrations']] == ['codex']
|
|
259
|
+
|
|
260
|
+
|
|
261
|
+
def test_modified_previously_connected_client_blocks_shared_update(tmp_path, monkeypatch):
|
|
262
|
+
from mlspace_mcp import client_setup
|
|
263
|
+
monkeypatch.setenv('HOME', str(tmp_path))
|
|
264
|
+
monkeypatch.setenv('XDG_CONFIG_HOME', str(tmp_path / '.config'))
|
|
265
|
+
monkeypatch.setenv('CODEX_HOME', str(tmp_path / '.codex'))
|
|
266
|
+
client_setup.install_clients({'opencode': '/bin/true'}, tmp_path / '.env', ['runtime-v1'])
|
|
267
|
+
config = tmp_path / '.config/opencode/opencode.json'
|
|
268
|
+
config.write_text(config.read_text().replace('runtime-v1', 'user-edited'))
|
|
269
|
+
with pytest.raises(ValueError, match='was not overwritten'):
|
|
270
|
+
client_setup.prepare_clients({'codex': '/bin/true'}, tmp_path / '.env', ['runtime-v2'])
|
|
271
|
+
|
|
272
|
+
|
|
273
|
+
def test_custom_client_config_cannot_be_silently_forgotten(tmp_path, monkeypatch):
|
|
274
|
+
from mlspace_mcp import client_setup
|
|
275
|
+
monkeypatch.setenv('HOME', str(tmp_path))
|
|
276
|
+
monkeypatch.setenv('XDG_CONFIG_HOME', str(tmp_path / '.config'))
|
|
277
|
+
monkeypatch.setenv('OPENCODE_CONFIG', str(tmp_path / 'custom.json'))
|
|
278
|
+
client_setup.install_clients({'opencode': '/bin/true'}, tmp_path / '.env', ['runtime-v1'])
|
|
279
|
+
monkeypatch.delenv('OPENCODE_CONFIG')
|
|
280
|
+
with pytest.raises(ValueError, match='OPENCODE_CONFIG'):
|
|
281
|
+
client_setup.prepare_clients({'opencode': '/bin/true'}, tmp_path / '.env', ['runtime-v2'])
|
|
282
|
+
assert not (tmp_path / '.config/opencode/opencode.json').exists()
|
|
283
|
+
|
|
284
|
+
|
|
285
|
+
def test_missing_managed_native_cli_blocks_before_new_registration(tmp_path, monkeypatch):
|
|
286
|
+
from mlspace_mcp import client_setup
|
|
287
|
+
monkeypatch.setenv('HOME', str(tmp_path))
|
|
288
|
+
monkeypatch.setenv('XDG_CONFIG_HOME', str(tmp_path / '.config'))
|
|
289
|
+
monkeypatch.setenv('CODEX_HOME', str(tmp_path / '.codex'))
|
|
290
|
+
monkeypatch.setattr(client_setup, 'detect_clients', lambda: {})
|
|
291
|
+
client_setup.atomic_write(tmp_path / '.codex/config.toml', '[mcp_servers.mlspace]\ncommand="old"\n')
|
|
292
|
+
client_setup.atomic_write(tmp_path / '.config/mlspace-plugin/install-state.json',
|
|
293
|
+
json.dumps({'clients': {'codex': 'old'}}))
|
|
294
|
+
with pytest.raises(ValueError, match='codex executable is unavailable'):
|
|
295
|
+
client_setup.install_clients({'opencode': '/bin/true'}, tmp_path / '.env', ['runtime-v2'])
|
|
296
|
+
assert not (tmp_path / '.config/opencode/opencode.json').exists()
|
|
@@ -163,7 +163,7 @@ def test_client_install_failure_preserves_previous_credentials(tmp_path, monkeyp
|
|
|
163
163
|
target.write_text(original)
|
|
164
164
|
monkeypatch.setattr('mlspace_mcp.init_cli._clients', lambda *a: {'opencode': '/bin/true'})
|
|
165
165
|
monkeypatch.setattr('mlspace_mcp.init_cli.server_command', lambda: ['/bin/echo'])
|
|
166
|
-
monkeypatch.setattr('mlspace_mcp.init_cli.prepare_clients', lambda *a: {})
|
|
166
|
+
monkeypatch.setattr('mlspace_mcp.init_cli.prepare_clients', lambda *a: {'registrations': [('opencode',)]})
|
|
167
167
|
def fail(*a, **kw):
|
|
168
168
|
raise ValueError('Client installation failed')
|
|
169
169
|
monkeypatch.setattr('mlspace_mcp.init_cli.install_clients', fail)
|
|
@@ -208,3 +208,66 @@ def test_human_init_connects_detected_client_and_skills_after_two_masked_prompts
|
|
|
208
208
|
output = capsys.readouterr()
|
|
209
209
|
assert 'human-secret' not in output.err + output.out
|
|
210
210
|
assert 'human-id' not in output.err + output.out
|
|
211
|
+
|
|
212
|
+
|
|
213
|
+
@pytest.mark.parametrize('kind', ['symlink', 'directory'])
|
|
214
|
+
@respx.mock
|
|
215
|
+
def test_invalid_credentials_target_fails_before_client_setup(tmp_path, monkeypatch, capsys, kind):
|
|
216
|
+
target = tmp_path / 'invalid.env'
|
|
217
|
+
if kind == 'symlink':
|
|
218
|
+
target.symlink_to(tmp_path / 'missing.env')
|
|
219
|
+
else:
|
|
220
|
+
target.mkdir()
|
|
221
|
+
calls = []
|
|
222
|
+
monkeypatch.setattr('mlspace_mcp.init_cli._clients', lambda *a: calls.append('clients') or {})
|
|
223
|
+
assert run_init(target, force=True, non_interactive=True, from_env=True) == 1
|
|
224
|
+
assert calls == []
|
|
225
|
+
assert 'Credentials target' in capsys.readouterr().err
|
|
226
|
+
|
|
227
|
+
|
|
228
|
+
@respx.mock
|
|
229
|
+
def test_final_save_failure_reports_connected_clients(tmp_path, monkeypatch, capsys):
|
|
230
|
+
routes()
|
|
231
|
+
target = tmp_path / '.env'
|
|
232
|
+
original = 'MLSPACE_READONLY=true\n'
|
|
233
|
+
target.write_text(original)
|
|
234
|
+
monkeypatch.setattr('mlspace_mcp.init_cli._clients', lambda *a: {'opencode': '/bin/true'})
|
|
235
|
+
monkeypatch.setattr('mlspace_mcp.init_cli.server_command', lambda: ['/bin/echo'])
|
|
236
|
+
def fail_save(*a):
|
|
237
|
+
raise PermissionError('sensitive detail should not be printed')
|
|
238
|
+
monkeypatch.setattr('mlspace_mcp.init_cli._write', fail_save)
|
|
239
|
+
assert run_init(target, force=True, base_url=BASE, non_interactive=True,
|
|
240
|
+
from_env=True, workspace_ids=['ws2']) == 1
|
|
241
|
+
assert target.read_text() == original
|
|
242
|
+
assert (tmp_path / '.config/opencode/opencode.json').exists()
|
|
243
|
+
error = capsys.readouterr().err
|
|
244
|
+
assert 'MCP and skills installed: opencode' in error
|
|
245
|
+
assert 'credentials could not be saved' in error
|
|
246
|
+
assert str(target) in error and '--force' in error
|
|
247
|
+
assert 'sensitive detail' not in error
|
|
248
|
+
|
|
249
|
+
|
|
250
|
+
@respx.mock
|
|
251
|
+
def test_existing_opencode_can_update_without_cli_or_client_selection(tmp_path, monkeypatch):
|
|
252
|
+
from mlspace_mcp import client_setup
|
|
253
|
+
routes()
|
|
254
|
+
target = tmp_path / '.env'
|
|
255
|
+
client_setup.install_clients({'opencode': '/missing/opencode'}, target, ['runtime-v1'])
|
|
256
|
+
monkeypatch.setattr(client_setup, 'detect_clients', lambda: {})
|
|
257
|
+
monkeypatch.setattr('mlspace_mcp.init_cli.detect_clients', lambda: {})
|
|
258
|
+
monkeypatch.setattr('mlspace_mcp.init_cli.server_command', lambda: ['runtime-v2'])
|
|
259
|
+
assert run_init(target, force=True, base_url=BASE, non_interactive=True,
|
|
260
|
+
from_env=True, workspace_ids=['ws2']) == 0
|
|
261
|
+
config = json.loads((tmp_path / '.config/opencode/opencode.json').read_text())
|
|
262
|
+
assert config['mcp']['mlspace']['command'] == ['runtime-v2']
|
|
263
|
+
|
|
264
|
+
|
|
265
|
+
def test_deleted_connections_are_not_automatically_recreated(tmp_path, monkeypatch):
|
|
266
|
+
from mlspace_mcp.client_setup import install_clients
|
|
267
|
+
from mlspace_mcp.init_cli import _clients
|
|
268
|
+
install_clients({'opencode': '/bin/true'}, tmp_path / '.env', ['runtime-v1'])
|
|
269
|
+
(tmp_path / '.config/opencode/opencode.json').write_text('{}')
|
|
270
|
+
monkeypatch.setattr('mlspace_mcp.init_cli.detect_clients', lambda: {'opencode': '/bin/true'})
|
|
271
|
+
with pytest.raises(ValueError, match='Select a new connection explicitly'):
|
|
272
|
+
_clients([], [], True)
|
|
273
|
+
assert _clients(['opencode'], [], True) == {'opencode': '/bin/true'}
|
mlspace_plugin-0.2.1/.gitignore
DELETED
|
@@ -1,53 +0,0 @@
|
|
|
1
|
-
# Python
|
|
2
|
-
__pycache__/
|
|
3
|
-
*.py[cod]
|
|
4
|
-
*.egg-info/
|
|
5
|
-
.eggs/
|
|
6
|
-
build/
|
|
7
|
-
dist/
|
|
8
|
-
.venv/
|
|
9
|
-
venv/
|
|
10
|
-
|
|
11
|
-
# Tooling
|
|
12
|
-
.pytest_cache/
|
|
13
|
-
.mypy_cache/
|
|
14
|
-
.ruff_cache/
|
|
15
|
-
.coverage
|
|
16
|
-
htmlcov/
|
|
17
|
-
|
|
18
|
-
# Env / secrets
|
|
19
|
-
.env
|
|
20
|
-
.env.*
|
|
21
|
-
!.env.example
|
|
22
|
-
*.secret
|
|
23
|
-
|
|
24
|
-
# OS
|
|
25
|
-
.DS_Store
|
|
26
|
-
|
|
27
|
-
# Local audit harnesses & artifacts (not part of the shipped product)
|
|
28
|
-
scripts/write_audit.py
|
|
29
|
-
scripts/api_audit.py
|
|
30
|
-
scripts/coverage_probe.py
|
|
31
|
-
docs/_*.json
|
|
32
|
-
docs/_*.png
|
|
33
|
-
_planning/api_evidence.txt
|
|
34
|
-
_planning/api_critique_and_prompts.json
|
|
35
|
-
|
|
36
|
-
# Agent worktrees (local scratch, never part of the product)
|
|
37
|
-
.claude/
|
|
38
|
-
.superpowers/
|
|
39
|
-
|
|
40
|
-
# Internal audit & planning artefacts (not part of the published server)
|
|
41
|
-
audit/
|
|
42
|
-
after_audit/
|
|
43
|
-
_planning/
|
|
44
|
-
docs/proposals/
|
|
45
|
-
docs/superpowers/
|
|
46
|
-
docs/DESIGN.md
|
|
47
|
-
docs/MLSPACE_API_*.md
|
|
48
|
-
docs/LLM_FRIENDLINESS_AUDIT.md
|
|
49
|
-
docs/E2E_TEST_REPORT.md
|
|
50
|
-
docs/EXEC_SUMMARY.md
|
|
51
|
-
docs/article_*.md
|
|
52
|
-
docs/slides*.md
|
|
53
|
-
docs/_*.json
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{mlspace_plugin-0.2.1 → mlspace_plugin-0.2.2}/plugin/skills/mlspace-diagnose-data-transfer/SKILL.md
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{mlspace_plugin-0.2.1 → mlspace_plugin-0.2.2}/plugin/skills/mlspace-launch-training-job/SKILL.md
RENAMED
|
File without changes
|
{mlspace_plugin-0.2.1 → mlspace_plugin-0.2.2}/plugin/skills/mlspace-list-running-jobs/SKILL.md
RENAMED
|
File without changes
|
|
File without changes
|
{mlspace_plugin-0.2.1 → mlspace_plugin-0.2.2}/plugin/skills/mlspace-stage-s3-to-nfs/SKILL.md
RENAMED
|
File without changes
|
{mlspace_plugin-0.2.1 → mlspace_plugin-0.2.2}/plugin/skills/mlspace-transfer-compute-node/SKILL.md
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|