elemctl 0.1.0__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.
- elemctl-0.1.0/LICENSE +21 -0
- elemctl-0.1.0/PKG-INFO +201 -0
- elemctl-0.1.0/README.md +173 -0
- elemctl-0.1.0/pyproject.toml +44 -0
- elemctl-0.1.0/setup.cfg +4 -0
- elemctl-0.1.0/src/elemctl/__init__.py +25 -0
- elemctl-0.1.0/src/elemctl/auth.py +142 -0
- elemctl-0.1.0/src/elemctl/build.py +288 -0
- elemctl-0.1.0/src/elemctl/cli.py +651 -0
- elemctl-0.1.0/src/elemctl/client.py +557 -0
- elemctl-0.1.0/src/elemctl/config.py +125 -0
- elemctl-0.1.0/src/elemctl/deploy.py +236 -0
- elemctl-0.1.0/src/elemctl/errors.py +54 -0
- elemctl-0.1.0/src/elemctl/mcp_server.py +210 -0
- elemctl-0.1.0/src/elemctl/transport.py +53 -0
- elemctl-0.1.0/src/elemctl/versions.py +45 -0
- elemctl-0.1.0/src/elemctl.egg-info/PKG-INFO +201 -0
- elemctl-0.1.0/src/elemctl.egg-info/SOURCES.txt +27 -0
- elemctl-0.1.0/src/elemctl.egg-info/dependency_links.txt +1 -0
- elemctl-0.1.0/src/elemctl.egg-info/entry_points.txt +2 -0
- elemctl-0.1.0/src/elemctl.egg-info/requires.txt +6 -0
- elemctl-0.1.0/src/elemctl.egg-info/top_level.txt +1 -0
- elemctl-0.1.0/tests/test_build.py +171 -0
- elemctl-0.1.0/tests/test_cli.py +145 -0
- elemctl-0.1.0/tests/test_client.py +231 -0
- elemctl-0.1.0/tests/test_config.py +89 -0
- elemctl-0.1.0/tests/test_deploy.py +238 -0
- elemctl-0.1.0/tests/test_mcp.py +52 -0
- elemctl-0.1.0/tests/test_versions.py +35 -0
elemctl-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 KeyFire
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
elemctl-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: elemctl
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: CLI, MCP-сервер и библиотека для Console API v2 платформы 1С:Предприятие.Элемент (1cmycloud)
|
|
5
|
+
Author: KeyFire
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Keywords: 1c,element,1cmycloud,console-api,deploy,mcp,xbsl
|
|
8
|
+
Classifier: Development Status :: 4 - Beta
|
|
9
|
+
Classifier: Environment :: Console
|
|
10
|
+
Classifier: Intended Audience :: Developers
|
|
11
|
+
Classifier: Natural Language :: Russian
|
|
12
|
+
Classifier: Operating System :: OS Independent
|
|
13
|
+
Classifier: Programming Language :: Python :: 3
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
19
|
+
Classifier: Topic :: Software Development :: Build Tools
|
|
20
|
+
Requires-Python: >=3.10
|
|
21
|
+
Description-Content-Type: text/markdown
|
|
22
|
+
License-File: LICENSE
|
|
23
|
+
Provides-Extra: mcp
|
|
24
|
+
Requires-Dist: mcp>=1.2; extra == "mcp"
|
|
25
|
+
Provides-Extra: dev
|
|
26
|
+
Requires-Dist: pytest>=7; extra == "dev"
|
|
27
|
+
Dynamic: license-file
|
|
28
|
+
|
|
29
|
+
# elemctl
|
|
30
|
+
|
|
31
|
+
Утилита командной строки, MCP-сервер и Python-библиотека для управления
|
|
32
|
+
приложениями облачной платформы **1С:Предприятие.Элемент** (1cmycloud.com)
|
|
33
|
+
через Console API v2.
|
|
34
|
+
|
|
35
|
+
elemctl закрывает жизненный цикл приложения на платформе без веб-консоли:
|
|
36
|
+
создать приложение, собрать архив сборки `.xasm`/`.xlib` из исходников
|
|
37
|
+
проекта, загрузить сборку, применить её к приложению и убедиться, что
|
|
38
|
+
применение действительно произошло (платформа умеет молча откатывать),
|
|
39
|
+
управлять ветками среды разработки, дампами и версией технологии. Один и
|
|
40
|
+
тот же движок доступен тремя способами: команда `elemctl` для терминала и
|
|
41
|
+
CI, MCP-сервер для AI-агентов (Claude Code и другие MCP-клиенты) и
|
|
42
|
+
python-модуль `elemctl` для собственных скриптов.
|
|
43
|
+
|
|
44
|
+
*elemctl is a CLI tool, MCP server and Python library for the
|
|
45
|
+
1C:Enterprise.Element (1cmycloud) Console API: manage applications, upload
|
|
46
|
+
builds and deploy with honest apply verification. Docs are in Russian - the
|
|
47
|
+
platform's audience - but the CLI output is plain JSON.*
|
|
48
|
+
|
|
49
|
+
## Возможности
|
|
50
|
+
|
|
51
|
+
- **Приложения**: список, карточка, создание, запуск, остановка, удаление,
|
|
52
|
+
версия технологии.
|
|
53
|
+
- **Проекты и сборки**: загрузка `.xasm`/`.xlib`, список сборок, удаление.
|
|
54
|
+
- **Сборка из исходников**: упаковка каталога проекта (`Проект.yaml` + модули)
|
|
55
|
+
в архив сборки с манифестом и git-метаданными, автоинкремент версии.
|
|
56
|
+
- **Деплой одной командой**: сборка -> загрузка -> применение -> перезапуск ->
|
|
57
|
+
**проверка фактического применения**.
|
|
58
|
+
- **Ветки среды разработки**: список, создание, привязка к приложению, merge,
|
|
59
|
+
загрузка изменений из git-ветки.
|
|
60
|
+
- **Дампы**: создание и контроль готовности.
|
|
61
|
+
- **MCP-сервер**: те же операции как инструменты для AI-агентов
|
|
62
|
+
(Claude Code и другие MCP-клиенты).
|
|
63
|
+
|
|
64
|
+
### Честная проверка применения
|
|
65
|
+
|
|
66
|
+
Особенность платформы: если применение проекта падает, платформа **молча
|
|
67
|
+
откатывает** приложение на предыдущую сборку - статус `Running` ничего не
|
|
68
|
+
говорит об успехе деплоя. `elemctl deploy` поэтому не верит статусу, а
|
|
69
|
+
проверяет после деплоя:
|
|
70
|
+
|
|
71
|
+
1. задачи приложения со статусом `Error`/`Failed`, начатые после старта
|
|
72
|
+
деплоя (старые ошибки из истории не учитываются);
|
|
73
|
+
2. фактическую версию проекта приложения (`source.project-version`) - она
|
|
74
|
+
должна совпасть с только что загруженной сборкой;
|
|
75
|
+
3. доступность uri приложения контрольным HTTP-запросом (информационно,
|
|
76
|
+
поле `uri-status` в отчёте: 401/403 нормальны для закрытых приложений).
|
|
77
|
+
|
|
78
|
+
Код возврата `deploy` равен нулю только если сборка действительно применилась.
|
|
79
|
+
|
|
80
|
+
## Установка
|
|
81
|
+
|
|
82
|
+
```bash
|
|
83
|
+
pipx install elemctl # или: pip install elemctl
|
|
84
|
+
pip install "elemctl[mcp]" # с MCP-сервером
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
Требуется Python 3.10+. Ядро и CLI не имеют внешних зависимостей
|
|
88
|
+
(только стандартная библиотека).
|
|
89
|
+
|
|
90
|
+
## Настройка
|
|
91
|
+
|
|
92
|
+
Реквизиты подключения берутся из переменных окружения либо из `.env`
|
|
93
|
+
в текущем каталоге (переменные окружения имеют приоритет):
|
|
94
|
+
|
|
95
|
+
| Переменная | Назначение |
|
|
96
|
+
|---|---|
|
|
97
|
+
| `ELEMENT_BASE_URL` | базовый URL платформы, например `https://1cmycloud.com` |
|
|
98
|
+
| `ELEMENT_CLIENT_ID` | Client-Id для получения токена |
|
|
99
|
+
| `ELEMENT_CLIENT_SECRET` | Client-Secret |
|
|
100
|
+
| `ELEMENT_APP_ID` | приложение по умолчанию (необязательно) |
|
|
101
|
+
| `ELEMENT_PROJECT_ID` | проект по умолчанию (необязательно) |
|
|
102
|
+
| `ELEMENT_SPACE_ID` | пространство по умолчанию (необязательно) |
|
|
103
|
+
| `ELEMENT_BRANCH_ID` | ветка платформы по умолчанию (необязательно) |
|
|
104
|
+
|
|
105
|
+
Client-Id/Client-Secret выпускаются в панели управления 1cmycloud
|
|
106
|
+
(раздел интеграций Console API). Шаблон файла - [.env.example](.env.example).
|
|
107
|
+
|
|
108
|
+
## Быстрый старт
|
|
109
|
+
|
|
110
|
+
```bash
|
|
111
|
+
# список приложений
|
|
112
|
+
elemctl apps list
|
|
113
|
+
|
|
114
|
+
# карточка приложения (статус, uri, фактическая версия проекта)
|
|
115
|
+
elemctl apps get <app-id>
|
|
116
|
+
|
|
117
|
+
# полный цикл деплоя из исходников с проверкой применения
|
|
118
|
+
elemctl deploy --app-id <app-id> --project-id <project-id> --project-dir acme/crm
|
|
119
|
+
|
|
120
|
+
# только собрать архив .xasm, никуда не загружая
|
|
121
|
+
elemctl build --project-dir acme/crm --output ./dist
|
|
122
|
+
|
|
123
|
+
# принять изменения ветки среды разработки
|
|
124
|
+
elemctl branches merge <branch-id>
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
Вывод всех команд - JSON в stdout; прогресс длительных операций - в stderr.
|
|
128
|
+
Ошибки возвращаются JSON-объектом с полем `error` и кодом возврата 1.
|
|
129
|
+
|
|
130
|
+
Полный список команд: `elemctl --help`, по группам - `elemctl apps --help`,
|
|
131
|
+
`elemctl deploy --help` и т.д.
|
|
132
|
+
|
|
133
|
+
## MCP-сервер
|
|
134
|
+
|
|
135
|
+
Сервер отдаёт операции платформы как MCP-инструменты (транспорт stdio):
|
|
136
|
+
|
|
137
|
+
```bash
|
|
138
|
+
pip install "elemctl[mcp]"
|
|
139
|
+
claude mcp add elemctl -- elemctl mcp
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
Реквизиты подключения сервер берёт из тех же переменных `ELEMENT_*` /
|
|
143
|
+
`.env`. Среди инструментов: `list_apps`, `get_app`, `deploy` (с полем `ok`
|
|
144
|
+
в ответе), `verify_deploy`, `list_builds`, `merge_branch` и другие.
|
|
145
|
+
|
|
146
|
+
## Использование как библиотеки
|
|
147
|
+
|
|
148
|
+
```python
|
|
149
|
+
from elemctl import Config, ElementClient
|
|
150
|
+
from elemctl.deploy import deploy_from_sources
|
|
151
|
+
|
|
152
|
+
client = ElementClient(Config.from_env())
|
|
153
|
+
apps = client.list_apps()
|
|
154
|
+
|
|
155
|
+
report = deploy_from_sources(
|
|
156
|
+
client,
|
|
157
|
+
app_id="...",
|
|
158
|
+
project_id="...",
|
|
159
|
+
project_dir="acme/crm",
|
|
160
|
+
log=print,
|
|
161
|
+
)
|
|
162
|
+
assert report.ok, report.problems
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
## Формат сборки
|
|
166
|
+
|
|
167
|
+
`.xasm` (приложение) и `.xlib` (библиотека) - это ZIP-архив:
|
|
168
|
+
|
|
169
|
+
```
|
|
170
|
+
Assembly.yaml # манифест: ProjectKind, Vendor, Name, Version, ...
|
|
171
|
+
{vendor}/{name}/... # файлы проекта: .yaml, .xbsl, ресурсы
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
Каталог проекта должен лежать по схеме `{repo}/{vendor}/{name}/Проект.yaml` -
|
|
175
|
+
пути в архиве строятся относительно корня репозитория. Вид проекта
|
|
176
|
+
(приложение/библиотека) определяется по полю `ВидПроекта` в `Проект.yaml`.
|
|
177
|
+
|
|
178
|
+
## Ограничения и статус
|
|
179
|
+
|
|
180
|
+
- Инструмент **неофициальный** и не аффилирован с фирмой "1С"; Console API
|
|
181
|
+
может меняться без предупреждения.
|
|
182
|
+
- Операция `branches sync` (загрузка изменений из git-ветки в приложение)
|
|
183
|
+
использует внутренний UI API платформы, а не Console API v2 - контракт
|
|
184
|
+
наименее стабильный.
|
|
185
|
+
- Создание приложения только по `--project-id` на части конфигураций платформы
|
|
186
|
+
даёт пустой каркас без данных проекта. Надёжный путь - источник-сборка:
|
|
187
|
+
`elemctl apps create <имя> --project-id <id> --latest-build` (MCP-инструмент
|
|
188
|
+
`create_app` подставляет последнюю сборку автоматически), а после создания -
|
|
189
|
+
`elemctl deploy`.
|
|
190
|
+
- Приложение с неопубликованными правками в среде разработки платформа
|
|
191
|
+
удалить не даёт (HTTP 400 `FAILED_PRECONDITION`), принудительного удаления
|
|
192
|
+
в Console API нет - только через панель управления; elemctl подскажет это
|
|
193
|
+
в тексте ошибки.
|
|
194
|
+
- Пересоздание приложения (delete + create) меняет его URL - внешние
|
|
195
|
+
настройки, завязанные на адрес (OIDC redirect и т.п.), придётся обновлять.
|
|
196
|
+
"Мягкой" очистки данных приложения в Console API нет, она выполняется в
|
|
197
|
+
консоли управления.
|
|
198
|
+
|
|
199
|
+
## Лицензия
|
|
200
|
+
|
|
201
|
+
[MIT](LICENSE)
|
elemctl-0.1.0/README.md
ADDED
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
# elemctl
|
|
2
|
+
|
|
3
|
+
Утилита командной строки, MCP-сервер и Python-библиотека для управления
|
|
4
|
+
приложениями облачной платформы **1С:Предприятие.Элемент** (1cmycloud.com)
|
|
5
|
+
через Console API v2.
|
|
6
|
+
|
|
7
|
+
elemctl закрывает жизненный цикл приложения на платформе без веб-консоли:
|
|
8
|
+
создать приложение, собрать архив сборки `.xasm`/`.xlib` из исходников
|
|
9
|
+
проекта, загрузить сборку, применить её к приложению и убедиться, что
|
|
10
|
+
применение действительно произошло (платформа умеет молча откатывать),
|
|
11
|
+
управлять ветками среды разработки, дампами и версией технологии. Один и
|
|
12
|
+
тот же движок доступен тремя способами: команда `elemctl` для терминала и
|
|
13
|
+
CI, MCP-сервер для AI-агентов (Claude Code и другие MCP-клиенты) и
|
|
14
|
+
python-модуль `elemctl` для собственных скриптов.
|
|
15
|
+
|
|
16
|
+
*elemctl is a CLI tool, MCP server and Python library for the
|
|
17
|
+
1C:Enterprise.Element (1cmycloud) Console API: manage applications, upload
|
|
18
|
+
builds and deploy with honest apply verification. Docs are in Russian - the
|
|
19
|
+
platform's audience - but the CLI output is plain JSON.*
|
|
20
|
+
|
|
21
|
+
## Возможности
|
|
22
|
+
|
|
23
|
+
- **Приложения**: список, карточка, создание, запуск, остановка, удаление,
|
|
24
|
+
версия технологии.
|
|
25
|
+
- **Проекты и сборки**: загрузка `.xasm`/`.xlib`, список сборок, удаление.
|
|
26
|
+
- **Сборка из исходников**: упаковка каталога проекта (`Проект.yaml` + модули)
|
|
27
|
+
в архив сборки с манифестом и git-метаданными, автоинкремент версии.
|
|
28
|
+
- **Деплой одной командой**: сборка -> загрузка -> применение -> перезапуск ->
|
|
29
|
+
**проверка фактического применения**.
|
|
30
|
+
- **Ветки среды разработки**: список, создание, привязка к приложению, merge,
|
|
31
|
+
загрузка изменений из git-ветки.
|
|
32
|
+
- **Дампы**: создание и контроль готовности.
|
|
33
|
+
- **MCP-сервер**: те же операции как инструменты для AI-агентов
|
|
34
|
+
(Claude Code и другие MCP-клиенты).
|
|
35
|
+
|
|
36
|
+
### Честная проверка применения
|
|
37
|
+
|
|
38
|
+
Особенность платформы: если применение проекта падает, платформа **молча
|
|
39
|
+
откатывает** приложение на предыдущую сборку - статус `Running` ничего не
|
|
40
|
+
говорит об успехе деплоя. `elemctl deploy` поэтому не верит статусу, а
|
|
41
|
+
проверяет после деплоя:
|
|
42
|
+
|
|
43
|
+
1. задачи приложения со статусом `Error`/`Failed`, начатые после старта
|
|
44
|
+
деплоя (старые ошибки из истории не учитываются);
|
|
45
|
+
2. фактическую версию проекта приложения (`source.project-version`) - она
|
|
46
|
+
должна совпасть с только что загруженной сборкой;
|
|
47
|
+
3. доступность uri приложения контрольным HTTP-запросом (информационно,
|
|
48
|
+
поле `uri-status` в отчёте: 401/403 нормальны для закрытых приложений).
|
|
49
|
+
|
|
50
|
+
Код возврата `deploy` равен нулю только если сборка действительно применилась.
|
|
51
|
+
|
|
52
|
+
## Установка
|
|
53
|
+
|
|
54
|
+
```bash
|
|
55
|
+
pipx install elemctl # или: pip install elemctl
|
|
56
|
+
pip install "elemctl[mcp]" # с MCP-сервером
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
Требуется Python 3.10+. Ядро и CLI не имеют внешних зависимостей
|
|
60
|
+
(только стандартная библиотека).
|
|
61
|
+
|
|
62
|
+
## Настройка
|
|
63
|
+
|
|
64
|
+
Реквизиты подключения берутся из переменных окружения либо из `.env`
|
|
65
|
+
в текущем каталоге (переменные окружения имеют приоритет):
|
|
66
|
+
|
|
67
|
+
| Переменная | Назначение |
|
|
68
|
+
|---|---|
|
|
69
|
+
| `ELEMENT_BASE_URL` | базовый URL платформы, например `https://1cmycloud.com` |
|
|
70
|
+
| `ELEMENT_CLIENT_ID` | Client-Id для получения токена |
|
|
71
|
+
| `ELEMENT_CLIENT_SECRET` | Client-Secret |
|
|
72
|
+
| `ELEMENT_APP_ID` | приложение по умолчанию (необязательно) |
|
|
73
|
+
| `ELEMENT_PROJECT_ID` | проект по умолчанию (необязательно) |
|
|
74
|
+
| `ELEMENT_SPACE_ID` | пространство по умолчанию (необязательно) |
|
|
75
|
+
| `ELEMENT_BRANCH_ID` | ветка платформы по умолчанию (необязательно) |
|
|
76
|
+
|
|
77
|
+
Client-Id/Client-Secret выпускаются в панели управления 1cmycloud
|
|
78
|
+
(раздел интеграций Console API). Шаблон файла - [.env.example](.env.example).
|
|
79
|
+
|
|
80
|
+
## Быстрый старт
|
|
81
|
+
|
|
82
|
+
```bash
|
|
83
|
+
# список приложений
|
|
84
|
+
elemctl apps list
|
|
85
|
+
|
|
86
|
+
# карточка приложения (статус, uri, фактическая версия проекта)
|
|
87
|
+
elemctl apps get <app-id>
|
|
88
|
+
|
|
89
|
+
# полный цикл деплоя из исходников с проверкой применения
|
|
90
|
+
elemctl deploy --app-id <app-id> --project-id <project-id> --project-dir acme/crm
|
|
91
|
+
|
|
92
|
+
# только собрать архив .xasm, никуда не загружая
|
|
93
|
+
elemctl build --project-dir acme/crm --output ./dist
|
|
94
|
+
|
|
95
|
+
# принять изменения ветки среды разработки
|
|
96
|
+
elemctl branches merge <branch-id>
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
Вывод всех команд - JSON в stdout; прогресс длительных операций - в stderr.
|
|
100
|
+
Ошибки возвращаются JSON-объектом с полем `error` и кодом возврата 1.
|
|
101
|
+
|
|
102
|
+
Полный список команд: `elemctl --help`, по группам - `elemctl apps --help`,
|
|
103
|
+
`elemctl deploy --help` и т.д.
|
|
104
|
+
|
|
105
|
+
## MCP-сервер
|
|
106
|
+
|
|
107
|
+
Сервер отдаёт операции платформы как MCP-инструменты (транспорт stdio):
|
|
108
|
+
|
|
109
|
+
```bash
|
|
110
|
+
pip install "elemctl[mcp]"
|
|
111
|
+
claude mcp add elemctl -- elemctl mcp
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
Реквизиты подключения сервер берёт из тех же переменных `ELEMENT_*` /
|
|
115
|
+
`.env`. Среди инструментов: `list_apps`, `get_app`, `deploy` (с полем `ok`
|
|
116
|
+
в ответе), `verify_deploy`, `list_builds`, `merge_branch` и другие.
|
|
117
|
+
|
|
118
|
+
## Использование как библиотеки
|
|
119
|
+
|
|
120
|
+
```python
|
|
121
|
+
from elemctl import Config, ElementClient
|
|
122
|
+
from elemctl.deploy import deploy_from_sources
|
|
123
|
+
|
|
124
|
+
client = ElementClient(Config.from_env())
|
|
125
|
+
apps = client.list_apps()
|
|
126
|
+
|
|
127
|
+
report = deploy_from_sources(
|
|
128
|
+
client,
|
|
129
|
+
app_id="...",
|
|
130
|
+
project_id="...",
|
|
131
|
+
project_dir="acme/crm",
|
|
132
|
+
log=print,
|
|
133
|
+
)
|
|
134
|
+
assert report.ok, report.problems
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
## Формат сборки
|
|
138
|
+
|
|
139
|
+
`.xasm` (приложение) и `.xlib` (библиотека) - это ZIP-архив:
|
|
140
|
+
|
|
141
|
+
```
|
|
142
|
+
Assembly.yaml # манифест: ProjectKind, Vendor, Name, Version, ...
|
|
143
|
+
{vendor}/{name}/... # файлы проекта: .yaml, .xbsl, ресурсы
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
Каталог проекта должен лежать по схеме `{repo}/{vendor}/{name}/Проект.yaml` -
|
|
147
|
+
пути в архиве строятся относительно корня репозитория. Вид проекта
|
|
148
|
+
(приложение/библиотека) определяется по полю `ВидПроекта` в `Проект.yaml`.
|
|
149
|
+
|
|
150
|
+
## Ограничения и статус
|
|
151
|
+
|
|
152
|
+
- Инструмент **неофициальный** и не аффилирован с фирмой "1С"; Console API
|
|
153
|
+
может меняться без предупреждения.
|
|
154
|
+
- Операция `branches sync` (загрузка изменений из git-ветки в приложение)
|
|
155
|
+
использует внутренний UI API платформы, а не Console API v2 - контракт
|
|
156
|
+
наименее стабильный.
|
|
157
|
+
- Создание приложения только по `--project-id` на части конфигураций платформы
|
|
158
|
+
даёт пустой каркас без данных проекта. Надёжный путь - источник-сборка:
|
|
159
|
+
`elemctl apps create <имя> --project-id <id> --latest-build` (MCP-инструмент
|
|
160
|
+
`create_app` подставляет последнюю сборку автоматически), а после создания -
|
|
161
|
+
`elemctl deploy`.
|
|
162
|
+
- Приложение с неопубликованными правками в среде разработки платформа
|
|
163
|
+
удалить не даёт (HTTP 400 `FAILED_PRECONDITION`), принудительного удаления
|
|
164
|
+
в Console API нет - только через панель управления; elemctl подскажет это
|
|
165
|
+
в тексте ошибки.
|
|
166
|
+
- Пересоздание приложения (delete + create) меняет его URL - внешние
|
|
167
|
+
настройки, завязанные на адрес (OIDC redirect и т.п.), придётся обновлять.
|
|
168
|
+
"Мягкой" очистки данных приложения в Console API нет, она выполняется в
|
|
169
|
+
консоли управления.
|
|
170
|
+
|
|
171
|
+
## Лицензия
|
|
172
|
+
|
|
173
|
+
[MIT](LICENSE)
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
# Метаданные пакета elemctl (раздел 1 спецификации).
|
|
2
|
+
|
|
3
|
+
[build-system]
|
|
4
|
+
requires = ["setuptools>=77"]
|
|
5
|
+
build-backend = "setuptools.build_meta"
|
|
6
|
+
|
|
7
|
+
[project]
|
|
8
|
+
name = "elemctl"
|
|
9
|
+
version = "0.1.0"
|
|
10
|
+
description = "CLI, MCP-сервер и библиотека для Console API v2 платформы 1С:Предприятие.Элемент (1cmycloud)"
|
|
11
|
+
readme = "README.md"
|
|
12
|
+
requires-python = ">=3.10"
|
|
13
|
+
license = "MIT"
|
|
14
|
+
license-files = ["LICENSE"]
|
|
15
|
+
authors = [{ name = "KeyFire" }]
|
|
16
|
+
keywords = ["1c", "element", "1cmycloud", "console-api", "deploy", "mcp", "xbsl"]
|
|
17
|
+
classifiers = [
|
|
18
|
+
"Development Status :: 4 - Beta",
|
|
19
|
+
"Environment :: Console",
|
|
20
|
+
"Intended Audience :: Developers",
|
|
21
|
+
"Natural Language :: Russian",
|
|
22
|
+
"Operating System :: OS Independent",
|
|
23
|
+
"Programming Language :: Python :: 3",
|
|
24
|
+
"Programming Language :: Python :: 3.10",
|
|
25
|
+
"Programming Language :: Python :: 3.11",
|
|
26
|
+
"Programming Language :: Python :: 3.12",
|
|
27
|
+
"Programming Language :: Python :: 3.13",
|
|
28
|
+
"Programming Language :: Python :: 3.14",
|
|
29
|
+
"Topic :: Software Development :: Build Tools",
|
|
30
|
+
]
|
|
31
|
+
dependencies = []
|
|
32
|
+
|
|
33
|
+
[project.optional-dependencies]
|
|
34
|
+
mcp = ["mcp>=1.2"]
|
|
35
|
+
dev = ["pytest>=7"]
|
|
36
|
+
|
|
37
|
+
[project.scripts]
|
|
38
|
+
elemctl = "elemctl.cli:main"
|
|
39
|
+
|
|
40
|
+
[tool.setuptools.packages.find]
|
|
41
|
+
where = ["src"]
|
|
42
|
+
|
|
43
|
+
[tool.pytest.ini_options]
|
|
44
|
+
testpaths = ["tests"]
|
elemctl-0.1.0/setup.cfg
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
"""elemctl – CLI, MCP-сервер и библиотека для Console API v2 платформы
|
|
2
|
+
1С:Предприятие.Элемент (1cmycloud).
|
|
3
|
+
|
|
4
|
+
Публичный API библиотеки:
|
|
5
|
+
|
|
6
|
+
from elemctl import Config, ElementClient
|
|
7
|
+
from elemctl.deploy import deploy_from_sources
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from .client import ElementClient
|
|
11
|
+
from .config import Config
|
|
12
|
+
from .errors import ApiError, BuildError, ConfigError, ElemctlError, TransportError
|
|
13
|
+
|
|
14
|
+
__version__ = "0.1.0"
|
|
15
|
+
|
|
16
|
+
__all__ = [
|
|
17
|
+
"ApiError",
|
|
18
|
+
"BuildError",
|
|
19
|
+
"Config",
|
|
20
|
+
"ConfigError",
|
|
21
|
+
"ElemctlError",
|
|
22
|
+
"ElementClient",
|
|
23
|
+
"TransportError",
|
|
24
|
+
"__version__",
|
|
25
|
+
]
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
"""Получение и кеширование токена Console API.
|
|
2
|
+
|
|
3
|
+
Токен живёт около часа, поэтому кешируется в файле в системном каталоге
|
|
4
|
+
временных файлов с TTL один час; ключ кеша различает пары base_url +
|
|
5
|
+
client_id. При 401 клиент запрашивает токен принудительно (force=True).
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
import base64
|
|
11
|
+
import hashlib
|
|
12
|
+
import json
|
|
13
|
+
import tempfile
|
|
14
|
+
import time
|
|
15
|
+
from pathlib import Path
|
|
16
|
+
|
|
17
|
+
from .errors import ApiError
|
|
18
|
+
|
|
19
|
+
TOKEN_TTL = 3600.0
|
|
20
|
+
# Запас до истечения: кеш считается годным, если жить осталось больше запаса.
|
|
21
|
+
EXPIRY_MARGIN = 30.0
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def extract_token(payload):
|
|
25
|
+
"""Достать токен из ответа сервера.
|
|
26
|
+
|
|
27
|
+
Токен лежит в первом непустом из полей id_token, token, value,
|
|
28
|
+
access_token. Особый случай: значение "Not implemented" токеном
|
|
29
|
+
не является и пропускается.
|
|
30
|
+
"""
|
|
31
|
+
if not isinstance(payload, dict):
|
|
32
|
+
return None
|
|
33
|
+
for key in ("id_token", "token", "value", "access_token"):
|
|
34
|
+
value = payload.get(key)
|
|
35
|
+
if not isinstance(value, str):
|
|
36
|
+
continue
|
|
37
|
+
value = value.strip()
|
|
38
|
+
if not value or value == "Not implemented":
|
|
39
|
+
continue
|
|
40
|
+
return value
|
|
41
|
+
return None
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
class TokenManager:
|
|
45
|
+
"""Выдаёт действующий Bearer-токен, пряча кеширование и обновление."""
|
|
46
|
+
|
|
47
|
+
def __init__(self, config, transport, cache_dir=None):
|
|
48
|
+
self._config = config
|
|
49
|
+
self._transport = transport
|
|
50
|
+
self._cache_dir = Path(cache_dir) if cache_dir else Path(tempfile.gettempdir())
|
|
51
|
+
self._token = None
|
|
52
|
+
self._expires_at = 0.0
|
|
53
|
+
|
|
54
|
+
def get_token(self, force=False):
|
|
55
|
+
"""Вернуть токен: из памяти, из файлового кеша или запросив новый."""
|
|
56
|
+
now = time.time()
|
|
57
|
+
if not force:
|
|
58
|
+
if self._token and now + EXPIRY_MARGIN < self._expires_at:
|
|
59
|
+
return self._token
|
|
60
|
+
cached = self._read_cache()
|
|
61
|
+
if cached and now + EXPIRY_MARGIN < cached.get("expires", 0):
|
|
62
|
+
self._token = cached["token"]
|
|
63
|
+
self._expires_at = cached["expires"]
|
|
64
|
+
return self._token
|
|
65
|
+
token = self._request_token()
|
|
66
|
+
self._token = token
|
|
67
|
+
self._expires_at = now + TOKEN_TTL
|
|
68
|
+
self._write_cache()
|
|
69
|
+
return token
|
|
70
|
+
|
|
71
|
+
# -- внутреннее -----------------------------------------------------
|
|
72
|
+
|
|
73
|
+
def _cache_path(self):
|
|
74
|
+
key = f"{self._config.base_url}|{self._config.client_id}"
|
|
75
|
+
digest = hashlib.sha256(key.encode("utf-8")).hexdigest()[:16]
|
|
76
|
+
return self._cache_dir / f"elemctl-token-{digest}.json"
|
|
77
|
+
|
|
78
|
+
def _read_cache(self):
|
|
79
|
+
try:
|
|
80
|
+
data = json.loads(self._cache_path().read_text(encoding="utf-8"))
|
|
81
|
+
except (OSError, ValueError):
|
|
82
|
+
return None
|
|
83
|
+
if isinstance(data, dict) and isinstance(data.get("token"), str):
|
|
84
|
+
return data
|
|
85
|
+
return None
|
|
86
|
+
|
|
87
|
+
def _write_cache(self):
|
|
88
|
+
try:
|
|
89
|
+
self._cache_dir.mkdir(parents=True, exist_ok=True)
|
|
90
|
+
payload = {"token": self._token, "expires": self._expires_at}
|
|
91
|
+
self._cache_path().write_text(
|
|
92
|
+
json.dumps(payload), encoding="utf-8"
|
|
93
|
+
)
|
|
94
|
+
except OSError:
|
|
95
|
+
# Кеш – только ускорение; его недоступность не должна ломать работу.
|
|
96
|
+
pass
|
|
97
|
+
|
|
98
|
+
def _request_token(self):
|
|
99
|
+
config = self._config.require()
|
|
100
|
+
credentials = f"{config.client_id}:{config.client_secret}".encode("utf-8")
|
|
101
|
+
basic = base64.b64encode(credentials).decode("ascii")
|
|
102
|
+
url = f"{config.base_url}/console/sys/token"
|
|
103
|
+
response = self._transport.request(
|
|
104
|
+
"POST",
|
|
105
|
+
url,
|
|
106
|
+
headers={
|
|
107
|
+
"Authorization": f"Basic {basic}",
|
|
108
|
+
"Content-Type": "application/x-www-form-urlencoded",
|
|
109
|
+
},
|
|
110
|
+
data=b"grant_type=client_credentials",
|
|
111
|
+
timeout=config.timeout,
|
|
112
|
+
)
|
|
113
|
+
if not 200 <= response.status < 300:
|
|
114
|
+
raise ApiError(
|
|
115
|
+
f"не удалось получить токен: HTTP {response.status}",
|
|
116
|
+
status=response.status,
|
|
117
|
+
method="POST",
|
|
118
|
+
url=url,
|
|
119
|
+
body=_safe_body(response),
|
|
120
|
+
)
|
|
121
|
+
try:
|
|
122
|
+
payload = response.json()
|
|
123
|
+
except ValueError:
|
|
124
|
+
payload = None
|
|
125
|
+
token = extract_token(payload)
|
|
126
|
+
if not token:
|
|
127
|
+
raise ApiError(
|
|
128
|
+
"токен не найден в ответе сервера (ожидались поля id_token, token, value или access_token)",
|
|
129
|
+
status=response.status,
|
|
130
|
+
method="POST",
|
|
131
|
+
url=url,
|
|
132
|
+
body=payload if payload is not None else response.text(),
|
|
133
|
+
)
|
|
134
|
+
return token
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
def _safe_body(response):
|
|
138
|
+
"""Тело ответа для деталей ошибки: JSON, а если не разбирается – текст."""
|
|
139
|
+
try:
|
|
140
|
+
return response.json()
|
|
141
|
+
except ValueError:
|
|
142
|
+
return response.text()
|