answer42 0.2.0__tar.gz → 0.2.1__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.
Files changed (58) hide show
  1. {answer42-0.2.0 → answer42-0.2.1}/.gitignore +1 -1
  2. {answer42-0.2.0 → answer42-0.2.1}/PKG-INFO +44 -146
  3. {answer42-0.2.0 → answer42-0.2.1}/README.md +43 -145
  4. answer42-0.2.1/docs/agent-installation.md +195 -0
  5. {answer42-0.2.0 → answer42-0.2.1}/pyproject.toml +1 -1
  6. answer42-0.2.1/scripts/build_pages.py +274 -0
  7. answer42-0.2.0/docs/publishing.md +0 -103
  8. {answer42-0.2.0 → answer42-0.2.1}/LICENSE +0 -0
  9. {answer42-0.2.0 → answer42-0.2.1}/credentials.example.json +0 -0
  10. {answer42-0.2.0 → answer42-0.2.1}/docs/architecture.md +0 -0
  11. {answer42-0.2.0 → answer42-0.2.1}/docs/assets/answer42-logo.png +0 -0
  12. {answer42-0.2.0 → answer42-0.2.1}/docs/installation.md +0 -0
  13. {answer42-0.2.0 → answer42-0.2.1}/scripts/build_cf.py +0 -0
  14. {answer42-0.2.0 → answer42-0.2.1}/scripts/e2e_stable.py +0 -0
  15. {answer42-0.2.0 → answer42-0.2.1}/scripts/openclaw_mcp_autoreload.py +0 -0
  16. {answer42-0.2.0 → answer42-0.2.1}/scripts/rag_cli.py +0 -0
  17. {answer42-0.2.0 → answer42-0.2.1}/src/cf/ConfigDumpInfo.xml +0 -0
  18. {answer42-0.2.0 → answer42-0.2.1}/src/cf/Configuration.xml +0 -0
  19. {answer42-0.2.0 → answer42-0.2.1}/src/cf/DataProcessors/MCPTestManager/Ext/ObjectModule.bsl +0 -0
  20. {answer42-0.2.0 → answer42-0.2.1}/src/cf/DataProcessors/MCPTestManager/Forms//320/244/320/276/321/200/320/274/320/260/Ext/Form/Module.bsl" +0 -0
  21. {answer42-0.2.0 → answer42-0.2.1}/src/cf/DataProcessors/MCPTestManager/Forms//320/244/320/276/321/200/320/274/320/260/Ext/Form.xml" +0 -0
  22. {answer42-0.2.0 → answer42-0.2.1}/src/cf/DataProcessors/MCPTestManager/Forms//320/244/320/276/321/200/320/274/320/260.xml" +0 -0
  23. {answer42-0.2.0 → answer42-0.2.1}/src/cf/DataProcessors/MCPTestManager.xml +0 -0
  24. {answer42-0.2.0 → answer42-0.2.1}/src/cf/Ext/ManagedApplicationModule.bsl +0 -0
  25. {answer42-0.2.0 → answer42-0.2.1}/src/cf/Languages//320/240/321/203/321/201/321/201/320/272/320/270/320/271.xml" +0 -0
  26. {answer42-0.2.0 → answer42-0.2.1}/src/client_cf/Catalogs//320/237/320/241_/320/222/320/273/320/260/320/264/320/265/320/273/320/265/321/206.xml" +0 -0
  27. {answer42-0.2.0 → answer42-0.2.1}/src/client_cf/Catalogs//320/237/320/241_/320/241/320/277/321/200/320/260/320/262/320/276/321/207/320/275/320/270/320/2721.xml" +0 -0
  28. {answer42-0.2.0 → answer42-0.2.1}/src/client_cf/ConfigDumpInfo.xml +0 -0
  29. {answer42-0.2.0 → answer42-0.2.1}/src/client_cf/Configuration.xml +0 -0
  30. {answer42-0.2.0 → answer42-0.2.1}/src/client_cf/Languages//320/240/321/203/321/201/321/201/320/272/320/270/320/271.xml" +0 -0
  31. {answer42-0.2.0 → answer42-0.2.1}/src/client_cf/Reports//320/237/320/241_/320/241/320/277/320/270/321/201/320/276/320/272/320/255/320/273/320/265/320/274/320/265/320/275/321/202/320/276/320/262/Ext/ManagerModule.bsl" +0 -0
  32. {answer42-0.2.0 → answer42-0.2.1}/src/client_cf/Reports//320/237/320/241_/320/241/320/277/320/270/321/201/320/276/320/272/320/255/320/273/320/265/320/274/320/265/320/275/321/202/320/276/320/262/Ext/ObjectModule.bsl" +0 -0
  33. {answer42-0.2.0 → answer42-0.2.1}/src/client_cf/Reports//320/237/320/241_/320/241/320/277/320/270/321/201/320/276/320/272/320/255/320/273/320/265/320/274/320/265/320/275/321/202/320/276/320/262/Templates//320/236/321/201/320/275/320/276/320/262/320/275/320/260/321/217/320/241/321/205/320/265/320/274/320/260/320/232/320/276/320/274/320/277/320/276/320/275/320/276/320/262/320/272/320/270/320/224/320/260/320/275/320/275/321/213/321/205/Ext/Template.xml" +0 -0
  34. {answer42-0.2.0 → answer42-0.2.1}/src/client_cf/Reports//320/237/320/241_/320/241/320/277/320/270/321/201/320/276/320/272/320/255/320/273/320/265/320/274/320/265/320/275/321/202/320/276/320/262/Templates//320/236/321/201/320/275/320/276/320/262/320/275/320/260/321/217/320/241/321/205/320/265/320/274/320/260/320/232/320/276/320/274/320/277/320/276/320/275/320/276/320/262/320/272/320/270/320/224/320/260/320/275/320/275/321/213/321/205.xml" +0 -0
  35. {answer42-0.2.0 → answer42-0.2.1}/src/client_cf/Reports//320/237/320/241_/320/241/320/277/320/270/321/201/320/276/320/272/320/255/320/273/320/265/320/274/320/265/320/275/321/202/320/276/320/262.xml" +0 -0
  36. {answer42-0.2.0 → answer42-0.2.1}/src/mcp_1c/__init__.py +0 -0
  37. {answer42-0.2.0 → answer42-0.2.1}/src/mcp_1c/assets/MCPTestClient.cf +0 -0
  38. {answer42-0.2.0 → answer42-0.2.1}/src/mcp_1c/assets/MCPTestManager.cf +0 -0
  39. {answer42-0.2.0 → answer42-0.2.1}/src/mcp_1c/assets/__init__.py +0 -0
  40. {answer42-0.2.0 → answer42-0.2.1}/src/mcp_1c/assets/skills/answer42/SKILL.md +0 -0
  41. {answer42-0.2.0 → answer42-0.2.1}/src/mcp_1c/assets/skills/answer42-rag/SKILL.md +0 -0
  42. {answer42-0.2.0 → answer42-0.2.1}/src/mcp_1c/bridge.py +0 -0
  43. {answer42-0.2.0 → answer42-0.2.1}/src/mcp_1c/credentials.py +0 -0
  44. {answer42-0.2.0 → answer42-0.2.1}/src/mcp_1c/os_support.py +0 -0
  45. {answer42-0.2.0 → answer42-0.2.1}/src/mcp_1c/platform.py +0 -0
  46. {answer42-0.2.0 → answer42-0.2.1}/src/mcp_1c/protocol.py +0 -0
  47. {answer42-0.2.0 → answer42-0.2.1}/src/mcp_1c/rag/__init__.py +0 -0
  48. {answer42-0.2.0 → answer42-0.2.1}/src/mcp_1c/rag/detect.py +0 -0
  49. {answer42-0.2.0 → answer42-0.2.1}/src/mcp_1c/rag/model.py +0 -0
  50. {answer42-0.2.0 → answer42-0.2.1}/src/mcp_1c/rag/parsers.py +0 -0
  51. {answer42-0.2.0 → answer42-0.2.1}/src/mcp_1c/rag/service.py +0 -0
  52. {answer42-0.2.0 → answer42-0.2.1}/src/mcp_1c/rag/store.py +0 -0
  53. {answer42-0.2.0 → answer42-0.2.1}/src/mcp_1c/recorder.py +0 -0
  54. {answer42-0.2.0 → answer42-0.2.1}/src/mcp_1c/release_helper.py +0 -0
  55. {answer42-0.2.0 → answer42-0.2.1}/src/mcp_1c/runtime.py +0 -0
  56. {answer42-0.2.0 → answer42-0.2.1}/src/mcp_1c/server.py +0 -0
  57. {answer42-0.2.0 → answer42-0.2.1}/src/mcp_1c/skill_installer.py +0 -0
  58. {answer42-0.2.0 → answer42-0.2.1}/src/mcp_1c/window_control.py +0 -0
@@ -22,4 +22,4 @@ dist/
22
22
 
23
23
  # BSL Language Server report (created at repo root)
24
24
  bsl-json.json
25
-
25
+ public/
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: answer42
3
- Version: 0.2.0
3
+ Version: 0.2.1
4
4
  Summary: Answer42 — The Answer to Life, Universe, and 1C — UI Driver. MCP-powered 1C:Enterprise UI automation: click, fill, navigate, test, and introspect managed forms through the test-client API
5
5
  Author: Marvin (AI Assistant), 42Clouds, and contributors
6
6
  Author-email: "Kosolapov Stanislav (proDOOMman)" <prodoomman@gmail.com>
@@ -35,7 +35,7 @@ Description-Content-Type: text/markdown
35
35
  # Answer42
36
36
 
37
37
  <p align="center">
38
- <img src="docs/assets/answer42-logo.png" alt="Answer42 logo" width="220">
38
+ <img src="https://gitlab.com/platform42/answer42-mcp/-/raw/beta/docs/assets/answer42-logo.png" alt="Answer42 logo" width="220">
39
39
  </p>
40
40
 
41
41
  **Answer42** — MCP-инструмент для интерактивного управления UI **1С:Предприятия** через клиент тестирования. Он позволяет AI-агенту открывать формы 1С, нажимать кнопки, заполнять поля, выбирать ссылки из форм выбора, работать с таблицами, динамическими списками и табличными документами.
@@ -86,6 +86,48 @@ start_session(
86
86
 
87
87
  Логин/пароль рекомендуется **не передавать явно**: `start_session` умеет брать их из локального credentials-файла по `base_url`. Параметры `username`/`password` остаются в API для разовых сценариев и обратной совместимости, но при передаче в MCP-вызове они видны вызывающему агенту в аргументах tool-call.
88
88
 
89
+ ## Установка skills
90
+
91
+ PyPI-пакет не записывает agent skills в пользовательские каталоги автоматически. После установки Answer42 поставьте bundled skills отдельной командой:
92
+
93
+ ```bash
94
+ answer42 install-skills
95
+ ```
96
+
97
+ По умолчанию команда устанавливает `answer42` и `answer42-rag` в OpenClaw:
98
+
99
+ ```text
100
+ ~/.openclaw/workspace/skills
101
+ ```
102
+
103
+ Другие агенты и варианты установки:
104
+
105
+ ```bash
106
+ answer42 install-skills --agent claude
107
+ answer42 install-skills --agent codex
108
+ answer42 install-skills --agent opencode
109
+ answer42 install-skills --agent pi
110
+ answer42 install-skills --agent hermes
111
+ answer42 install-skills --agent all
112
+ answer42 install-skills --target-dir /path/to/skills
113
+ answer42 install-skills --list-agents
114
+ answer42 install-skills --dry-run --agent all
115
+ ```
116
+
117
+ Для агентов без стабильного стандарта skill-директорий (`codex`, `opencode`, `pi`, `hermes`) presets best-effort. Если конкретная установка использует другой путь, укажите `--target-dir` явно.
118
+
119
+ ## Инструкция для AI-агентов
120
+
121
+ Если вы AI-агент и пользователь просит установить Answer42 и skills, используйте отдельную инструкцию: [`docs/agent-installation.md`](docs/agent-installation.md).
122
+
123
+ Коротко:
124
+
125
+ 1. установите пакет через `pipx` или virtualenv;
126
+ 2. выполните `answer42 install-skills`;
127
+ 3. подготовьте `ONEC_MCP_CREDENTIALS_FILE` вне репозитория;
128
+ 4. зарегистрируйте MCP-сервер в агентском клиенте;
129
+ 5. проверьте установку через `credentials_check`, затем smoke-сессию `start_session` → `active_window` → `stop_session`.
130
+
89
131
  ## Безопасное хранение логинов и паролей
90
132
 
91
133
  Чтобы креды были доступны MCP-серверу, но не попадали в чат и аргументы tool-call, храните их в локальном файле за пределами репозитория и передайте путь в окружение процесса Answer42:
@@ -229,88 +271,6 @@ E2E_PARALLEL=1 E2E_SESSION_ID=e2e-split python3 scripts/e2e_stable.py
229
271
 
230
272
  `start_session` также переиспользует общий автономный сервер для одинаковой файловой базы. Последний `stop_session` освобождает refcount и завершает shared `ibsrv`.
231
273
 
232
- ## MCP-инструменты
233
-
234
- ### Жизненный цикл
235
-
236
- - `start_session(...)` — единый запуск: определение версии по `base_url`, Xvfb на Linux при необходимости, свободные порты, bridge, файловая база менеджера, `ibsrv` или fallback `file-direct`, test-manager и подключение клиента тестирования. Если `username`/`password` не переданы, берёт их из credentials-файла по `base_url`. Поддерживает `extra_args`, `execute` → `/Execute`, `command_parameter` → `/C`; несколько частей `/C` объединяются через `;`.
237
- - `session_status(session_id)` — состояние bridge, runtime, клиента тестирования и RAG snapshot.
238
- - `stop_session(session_id, clean_data)` — отключение клиента тестирования и остановка всех процессов, Xvfb и bridge.
239
-
240
- ### Автоматизация UI
241
-
242
- - `active_window()` — активное окно тестируемого приложения через `ТестируемоеПриложение.ПолучитьАктивноеОкно()`.
243
- - `windows_list(max_depth)` — список окон тестируемого приложения; если открыто два окна клиента, возвращает оба, потому что читает подчинённые объекты `ТестируемоеПриложение`, а не окна менеджера тестирования.
244
- - `activate_window(index, title, name)` — переключить активное окно по индексу, заголовку или имени.
245
- - `goto_start_page()`, `goto_previous_window()`, `goto_next_window()` — навигация основного окна: `ПерейтиКНачальнойСтранице`, `ПерейтиКПредыдущемуОкну`, `ПерейтиКСледующемуОкну`.
246
- - `ui_tree(max_depth, include_rag_types, include_hidden, rag_snapshot)` — дерево UI с `visible`, `available`, `enabled`, `read_only`; скрытые элементы и неактивные страницы page-group по умолчанию опускаются, `include_hidden=true` возвращает полное дерево. RAG-типы добавляются, когда доступен индекс метаданных.
247
- - `command_bar()` — диагностическая информация по командной панели, когда она доступна API тест-клиента.
248
- - `current_element()` — диагностическая информация по текущему элементу, когда она доступна API тест-клиента.
249
- - `find_object(name, title, type)` — найти объект в дереве UI.
250
- - `activate_object(name, title, type)` — активировать объект.
251
- - `open_navigation_link(navigation_link)` — открыть навигационную ссылку `e1cib/...`.
252
- - `click_button(name, search_by_title)` — нажать кнопку.
253
- - `set_field_value(field_name, value, search_by_title)` — установить значение поля.
254
- - `create_new_item()` — создать новый элемент.
255
- - `save_form()` — сохранить форму.
256
- - `close_form()` — закрыть форму.
257
- - `screenshot(path)` — скриншот экрана.
258
-
259
- ### Таблицы и табличные части
260
-
261
- - `table_rows(...)` — строки таблицы.
262
- - `table_find_row(...)` — найти строку по подстроке.
263
- - `table_goto_row(...)` — перейти к строке.
264
- - `table_current_row(...)` — текущая строка.
265
- - `table_selected_rows(...)` — выделенные строки.
266
- - `table_set_field(...)` — установить значение в ячейке.
267
- - `table_choose_field_from_list(...)` — открыть форму выбора для ссылочного поля в строке табличной части.
268
- - `table_edit_info(...)` — информация о редактировании таблицы.
269
- - `table_move_row(...)` — перейти к первой/следующей/предыдущей/последней строке.
270
-
271
- ### Динамические списки
272
-
273
- - `dynamic_list_available_fields(object, rag_snapshot)` — поля, доступные для отбора/сортировки по RAG-индексу.
274
- - `dynamic_list_apply_settings(filters, orders, clear_first)` — применить сортировки через API тест-клиента и best-effort отборы через стандартную форму **Настроить список**.
275
- - `dynamic_list_set_order(column_title, name="Список")` — сортировка по видимому заголовку колонки.
276
- - `dynamic_list_output()` — команда **Вывести список** с возвратом текста табличного документа, когда он доступен.
277
- - `dynamic_list_open_settings()` — открыть **Настроить список**.
278
- - `dynamic_list_clear_settings()` — сбросить настройки динамического списка.
279
- - `dynamic_list_open_form_settings()` — открыть **Изменить форму**.
280
-
281
- ### Табличные документы
282
-
283
- - `tabular_documents(include_text)` — список табличных документов в активном окне.
284
- - `tabular_document_text(...)` — текст табличного документа.
285
- - `tabular_document_save(path, format)` — сохранить табличный документ в выбранном формате.
286
-
287
-
288
- ### Доказательная запись
289
-
290
- - `recording_start(output_dir, display, window)` — включить доказательную запись.
291
- - `recording_capture(action, note)` — вручную добавить доказательный слайд.
292
- - `recording_stop()` — собрать `manifest.json`, `recording.pdf` (если доступен backend PDF).
293
-
294
- ### RAG / индекс метаданных
295
-
296
- - `rag_source_add(name, path, kind, format="auto")` — зарегистрировать источник; формат определяется автоматически при `format="auto"`.
297
- - `rag_sources_list()` — список источников.
298
- - `rag_snapshot_create(name, base, extensions, sources)` — создать effective snapshot.
299
- - `rag_snapshots_list()` — список snapshot-ов.
300
- - `rag_index_build(snapshot, source)` — построить индекс.
301
- - `rag_lookup_object(object, snapshot)` — найти объект метаданных.
302
- - `rag_query(query, snapshot, limit)` — полнотекстовый поиск.
303
-
304
- CLI-пример:
305
-
306
- ```bash
307
- python3 scripts/rag_cli.py add-source Configuration /path/to/configuration/src --kind base
308
- python3 scripts/rag_cli.py add-source Extension1 /path/to/extension/src --kind extension
309
- python3 scripts/rag_cli.py snapshot Configuration+Extension1 --base Configuration --extension Extension1
310
- python3 scripts/rag_cli.py build --snapshot Configuration+Extension1
311
- python3 scripts/rag_cli.py lookup РегистрСведений.Регистр1 --snapshot Configuration+Extension1
312
- ```
313
-
314
274
  ## Ограничения
315
275
 
316
276
  - Форма пользовательской настройки **«Изменить форму»** частично недоступна для надёжной автоматизации через API клиента тестирования. В частности, команда **«Добавить поля»** в верхней панели формы настройки может быть видна на скриншоте и в диагностическом тексте, но не нажиматься как обычная `ТестируемаяКнопкаФормы`: `click_button` может не находить её, а `activate_object` может вернуть `activated=true` без открытия диалога добавления полей.
@@ -324,65 +284,3 @@ MIT
324
284
  Copyright (c) 2026 Kosolapov Stanislav aka proDOOMman <prodoomman@gmail.com>, Marvin (AI Assistant), 42Clouds, and contributors.
325
285
 
326
286
  Licensed under the MIT License.
327
-
328
- ## Публикация пакета
329
-
330
- Рекомендуемый канал установки для пользователей — PyPI:
331
-
332
- ```bash
333
- pipx install 'answer42[screenshot,linux-window-control]'
334
- # Windows:
335
- # pipx install "answer42[screenshot,windows-window-control]"
336
- ```
337
-
338
- Публикация автоматизирована в `.gitlab-ci.yml` и оставлена только для PyPI:
339
-
340
- 1. tag `vX.Y.Z` запускает unit/ruff/package checks;
341
- 2. `package` собирает wheel/sdist и проверяет их через `twine check`;
342
- 3. `publish_pypi` публикует wheel/sdist в PyPI через `PYPI_API_TOKEN`;
343
- 4. `create_gitlab_release` создаёт GitLab Release для tag.
344
-
345
-
346
- One-command релиз:
347
-
348
- ```bash
349
- answer42 release 0.2.1
350
- ```
351
-
352
- Команда меняет `pyproject.toml`, создаёт commit `Release v0.2.1`, annotated tag `v0.2.1`, пушит branch и tag. Tag запускает CI: публикацию в PyPI и создание GitLab Release.
353
-
354
- Секрет GitLab CI:
355
-
356
- - `PYPI_API_TOKEN` — PyPI token для проекта `answer42`.
357
-
358
- Перед релизом проверьте, что `src/mcp_1c/assets/MCPTestManager.cf` и `src/mcp_1c/assets/MCPTestClient.cf` пересобраны из актуальных XML-исходников. Runtime использует XML+`scripts/build_cf.py` в git checkout, а в установленном PyPI wheel при отсутствии исходников копирует packaged `.cf` из `mcp_1c.assets`.
359
-
360
- ### Установка packaged skills
361
-
362
- PyPI сам по себе не устанавливает agent skills в пользовательские каталоги, поэтому в CLI добавлена команда:
363
-
364
- ```bash
365
- answer42 install-skills
366
- ```
367
-
368
- По умолчанию она устанавливает `answer42` и `answer42-rag` в OpenClaw:
369
-
370
- ```text
371
- ~/.openclaw/workspace/skills
372
- ```
373
-
374
- Другие агенты/варианты:
375
-
376
- ```bash
377
- answer42 install-skills --agent claude
378
- answer42 install-skills --agent codex
379
- answer42 install-skills --agent opencode
380
- answer42 install-skills --agent pi
381
- answer42 install-skills --agent hermes
382
- answer42 install-skills --agent all
383
- answer42 install-skills --target-dir /path/to/skills
384
- answer42 install-skills --list-agents
385
- answer42 install-skills --dry-run --agent all
386
- ```
387
-
388
- Для агентов без стабильного стандарта skill-директорий (`codex`, `opencode`, `pi`, `hermes`) presets best-effort; если у конкретной установки другой путь, используйте `--target-dir`.
@@ -1,7 +1,7 @@
1
1
  # Answer42
2
2
 
3
3
  <p align="center">
4
- <img src="docs/assets/answer42-logo.png" alt="Answer42 logo" width="220">
4
+ <img src="https://gitlab.com/platform42/answer42-mcp/-/raw/beta/docs/assets/answer42-logo.png" alt="Answer42 logo" width="220">
5
5
  </p>
6
6
 
7
7
  **Answer42** — MCP-инструмент для интерактивного управления UI **1С:Предприятия** через клиент тестирования. Он позволяет AI-агенту открывать формы 1С, нажимать кнопки, заполнять поля, выбирать ссылки из форм выбора, работать с таблицами, динамическими списками и табличными документами.
@@ -52,6 +52,48 @@ start_session(
52
52
 
53
53
  Логин/пароль рекомендуется **не передавать явно**: `start_session` умеет брать их из локального credentials-файла по `base_url`. Параметры `username`/`password` остаются в API для разовых сценариев и обратной совместимости, но при передаче в MCP-вызове они видны вызывающему агенту в аргументах tool-call.
54
54
 
55
+ ## Установка skills
56
+
57
+ PyPI-пакет не записывает agent skills в пользовательские каталоги автоматически. После установки Answer42 поставьте bundled skills отдельной командой:
58
+
59
+ ```bash
60
+ answer42 install-skills
61
+ ```
62
+
63
+ По умолчанию команда устанавливает `answer42` и `answer42-rag` в OpenClaw:
64
+
65
+ ```text
66
+ ~/.openclaw/workspace/skills
67
+ ```
68
+
69
+ Другие агенты и варианты установки:
70
+
71
+ ```bash
72
+ answer42 install-skills --agent claude
73
+ answer42 install-skills --agent codex
74
+ answer42 install-skills --agent opencode
75
+ answer42 install-skills --agent pi
76
+ answer42 install-skills --agent hermes
77
+ answer42 install-skills --agent all
78
+ answer42 install-skills --target-dir /path/to/skills
79
+ answer42 install-skills --list-agents
80
+ answer42 install-skills --dry-run --agent all
81
+ ```
82
+
83
+ Для агентов без стабильного стандарта skill-директорий (`codex`, `opencode`, `pi`, `hermes`) presets best-effort. Если конкретная установка использует другой путь, укажите `--target-dir` явно.
84
+
85
+ ## Инструкция для AI-агентов
86
+
87
+ Если вы AI-агент и пользователь просит установить Answer42 и skills, используйте отдельную инструкцию: [`docs/agent-installation.md`](docs/agent-installation.md).
88
+
89
+ Коротко:
90
+
91
+ 1. установите пакет через `pipx` или virtualenv;
92
+ 2. выполните `answer42 install-skills`;
93
+ 3. подготовьте `ONEC_MCP_CREDENTIALS_FILE` вне репозитория;
94
+ 4. зарегистрируйте MCP-сервер в агентском клиенте;
95
+ 5. проверьте установку через `credentials_check`, затем smoke-сессию `start_session` → `active_window` → `stop_session`.
96
+
55
97
  ## Безопасное хранение логинов и паролей
56
98
 
57
99
  Чтобы креды были доступны MCP-серверу, но не попадали в чат и аргументы tool-call, храните их в локальном файле за пределами репозитория и передайте путь в окружение процесса Answer42:
@@ -195,88 +237,6 @@ E2E_PARALLEL=1 E2E_SESSION_ID=e2e-split python3 scripts/e2e_stable.py
195
237
 
196
238
  `start_session` также переиспользует общий автономный сервер для одинаковой файловой базы. Последний `stop_session` освобождает refcount и завершает shared `ibsrv`.
197
239
 
198
- ## MCP-инструменты
199
-
200
- ### Жизненный цикл
201
-
202
- - `start_session(...)` — единый запуск: определение версии по `base_url`, Xvfb на Linux при необходимости, свободные порты, bridge, файловая база менеджера, `ibsrv` или fallback `file-direct`, test-manager и подключение клиента тестирования. Если `username`/`password` не переданы, берёт их из credentials-файла по `base_url`. Поддерживает `extra_args`, `execute` → `/Execute`, `command_parameter` → `/C`; несколько частей `/C` объединяются через `;`.
203
- - `session_status(session_id)` — состояние bridge, runtime, клиента тестирования и RAG snapshot.
204
- - `stop_session(session_id, clean_data)` — отключение клиента тестирования и остановка всех процессов, Xvfb и bridge.
205
-
206
- ### Автоматизация UI
207
-
208
- - `active_window()` — активное окно тестируемого приложения через `ТестируемоеПриложение.ПолучитьАктивноеОкно()`.
209
- - `windows_list(max_depth)` — список окон тестируемого приложения; если открыто два окна клиента, возвращает оба, потому что читает подчинённые объекты `ТестируемоеПриложение`, а не окна менеджера тестирования.
210
- - `activate_window(index, title, name)` — переключить активное окно по индексу, заголовку или имени.
211
- - `goto_start_page()`, `goto_previous_window()`, `goto_next_window()` — навигация основного окна: `ПерейтиКНачальнойСтранице`, `ПерейтиКПредыдущемуОкну`, `ПерейтиКСледующемуОкну`.
212
- - `ui_tree(max_depth, include_rag_types, include_hidden, rag_snapshot)` — дерево UI с `visible`, `available`, `enabled`, `read_only`; скрытые элементы и неактивные страницы page-group по умолчанию опускаются, `include_hidden=true` возвращает полное дерево. RAG-типы добавляются, когда доступен индекс метаданных.
213
- - `command_bar()` — диагностическая информация по командной панели, когда она доступна API тест-клиента.
214
- - `current_element()` — диагностическая информация по текущему элементу, когда она доступна API тест-клиента.
215
- - `find_object(name, title, type)` — найти объект в дереве UI.
216
- - `activate_object(name, title, type)` — активировать объект.
217
- - `open_navigation_link(navigation_link)` — открыть навигационную ссылку `e1cib/...`.
218
- - `click_button(name, search_by_title)` — нажать кнопку.
219
- - `set_field_value(field_name, value, search_by_title)` — установить значение поля.
220
- - `create_new_item()` — создать новый элемент.
221
- - `save_form()` — сохранить форму.
222
- - `close_form()` — закрыть форму.
223
- - `screenshot(path)` — скриншот экрана.
224
-
225
- ### Таблицы и табличные части
226
-
227
- - `table_rows(...)` — строки таблицы.
228
- - `table_find_row(...)` — найти строку по подстроке.
229
- - `table_goto_row(...)` — перейти к строке.
230
- - `table_current_row(...)` — текущая строка.
231
- - `table_selected_rows(...)` — выделенные строки.
232
- - `table_set_field(...)` — установить значение в ячейке.
233
- - `table_choose_field_from_list(...)` — открыть форму выбора для ссылочного поля в строке табличной части.
234
- - `table_edit_info(...)` — информация о редактировании таблицы.
235
- - `table_move_row(...)` — перейти к первой/следующей/предыдущей/последней строке.
236
-
237
- ### Динамические списки
238
-
239
- - `dynamic_list_available_fields(object, rag_snapshot)` — поля, доступные для отбора/сортировки по RAG-индексу.
240
- - `dynamic_list_apply_settings(filters, orders, clear_first)` — применить сортировки через API тест-клиента и best-effort отборы через стандартную форму **Настроить список**.
241
- - `dynamic_list_set_order(column_title, name="Список")` — сортировка по видимому заголовку колонки.
242
- - `dynamic_list_output()` — команда **Вывести список** с возвратом текста табличного документа, когда он доступен.
243
- - `dynamic_list_open_settings()` — открыть **Настроить список**.
244
- - `dynamic_list_clear_settings()` — сбросить настройки динамического списка.
245
- - `dynamic_list_open_form_settings()` — открыть **Изменить форму**.
246
-
247
- ### Табличные документы
248
-
249
- - `tabular_documents(include_text)` — список табличных документов в активном окне.
250
- - `tabular_document_text(...)` — текст табличного документа.
251
- - `tabular_document_save(path, format)` — сохранить табличный документ в выбранном формате.
252
-
253
-
254
- ### Доказательная запись
255
-
256
- - `recording_start(output_dir, display, window)` — включить доказательную запись.
257
- - `recording_capture(action, note)` — вручную добавить доказательный слайд.
258
- - `recording_stop()` — собрать `manifest.json`, `recording.pdf` (если доступен backend PDF).
259
-
260
- ### RAG / индекс метаданных
261
-
262
- - `rag_source_add(name, path, kind, format="auto")` — зарегистрировать источник; формат определяется автоматически при `format="auto"`.
263
- - `rag_sources_list()` — список источников.
264
- - `rag_snapshot_create(name, base, extensions, sources)` — создать effective snapshot.
265
- - `rag_snapshots_list()` — список snapshot-ов.
266
- - `rag_index_build(snapshot, source)` — построить индекс.
267
- - `rag_lookup_object(object, snapshot)` — найти объект метаданных.
268
- - `rag_query(query, snapshot, limit)` — полнотекстовый поиск.
269
-
270
- CLI-пример:
271
-
272
- ```bash
273
- python3 scripts/rag_cli.py add-source Configuration /path/to/configuration/src --kind base
274
- python3 scripts/rag_cli.py add-source Extension1 /path/to/extension/src --kind extension
275
- python3 scripts/rag_cli.py snapshot Configuration+Extension1 --base Configuration --extension Extension1
276
- python3 scripts/rag_cli.py build --snapshot Configuration+Extension1
277
- python3 scripts/rag_cli.py lookup РегистрСведений.Регистр1 --snapshot Configuration+Extension1
278
- ```
279
-
280
240
  ## Ограничения
281
241
 
282
242
  - Форма пользовательской настройки **«Изменить форму»** частично недоступна для надёжной автоматизации через API клиента тестирования. В частности, команда **«Добавить поля»** в верхней панели формы настройки может быть видна на скриншоте и в диагностическом тексте, но не нажиматься как обычная `ТестируемаяКнопкаФормы`: `click_button` может не находить её, а `activate_object` может вернуть `activated=true` без открытия диалога добавления полей.
@@ -290,65 +250,3 @@ MIT
290
250
  Copyright (c) 2026 Kosolapov Stanislav aka proDOOMman <prodoomman@gmail.com>, Marvin (AI Assistant), 42Clouds, and contributors.
291
251
 
292
252
  Licensed under the MIT License.
293
-
294
- ## Публикация пакета
295
-
296
- Рекомендуемый канал установки для пользователей — PyPI:
297
-
298
- ```bash
299
- pipx install 'answer42[screenshot,linux-window-control]'
300
- # Windows:
301
- # pipx install "answer42[screenshot,windows-window-control]"
302
- ```
303
-
304
- Публикация автоматизирована в `.gitlab-ci.yml` и оставлена только для PyPI:
305
-
306
- 1. tag `vX.Y.Z` запускает unit/ruff/package checks;
307
- 2. `package` собирает wheel/sdist и проверяет их через `twine check`;
308
- 3. `publish_pypi` публикует wheel/sdist в PyPI через `PYPI_API_TOKEN`;
309
- 4. `create_gitlab_release` создаёт GitLab Release для tag.
310
-
311
-
312
- One-command релиз:
313
-
314
- ```bash
315
- answer42 release 0.2.1
316
- ```
317
-
318
- Команда меняет `pyproject.toml`, создаёт commit `Release v0.2.1`, annotated tag `v0.2.1`, пушит branch и tag. Tag запускает CI: публикацию в PyPI и создание GitLab Release.
319
-
320
- Секрет GitLab CI:
321
-
322
- - `PYPI_API_TOKEN` — PyPI token для проекта `answer42`.
323
-
324
- Перед релизом проверьте, что `src/mcp_1c/assets/MCPTestManager.cf` и `src/mcp_1c/assets/MCPTestClient.cf` пересобраны из актуальных XML-исходников. Runtime использует XML+`scripts/build_cf.py` в git checkout, а в установленном PyPI wheel при отсутствии исходников копирует packaged `.cf` из `mcp_1c.assets`.
325
-
326
- ### Установка packaged skills
327
-
328
- PyPI сам по себе не устанавливает agent skills в пользовательские каталоги, поэтому в CLI добавлена команда:
329
-
330
- ```bash
331
- answer42 install-skills
332
- ```
333
-
334
- По умолчанию она устанавливает `answer42` и `answer42-rag` в OpenClaw:
335
-
336
- ```text
337
- ~/.openclaw/workspace/skills
338
- ```
339
-
340
- Другие агенты/варианты:
341
-
342
- ```bash
343
- answer42 install-skills --agent claude
344
- answer42 install-skills --agent codex
345
- answer42 install-skills --agent opencode
346
- answer42 install-skills --agent pi
347
- answer42 install-skills --agent hermes
348
- answer42 install-skills --agent all
349
- answer42 install-skills --target-dir /path/to/skills
350
- answer42 install-skills --list-agents
351
- answer42 install-skills --dry-run --agent all
352
- ```
353
-
354
- Для агентов без стабильного стандарта skill-директорий (`codex`, `opencode`, `pi`, `hermes`) presets best-effort; если у конкретной установки другой путь, используйте `--target-dir`.
@@ -0,0 +1,195 @@
1
+ # Инструкция для AI-агентов: установка Answer42 и skills
2
+
3
+ Эта инструкция предназначена для AI-агентов, которые устанавливают Answer42 в пользовательское окружение и подключают MCP-сервер к агентскому клиенту.
4
+
5
+ ## 1. Проверьте окружение
6
+
7
+ 1. Определите ОС и способ запуска агента.
8
+ 2. Убедитесь, что доступна Python 3.11+.
9
+ 3. Убедитесь, что установлена платформа 1С:Предприятие 8.3.27+.
10
+ 4. Для Linux нужен GUI/X11 или Xvfb. Headless-сервер без X11 не подходит для скриншотов и запуска GUI-клиента 1С.
11
+ 5. Для Windows запускайте Answer42 только из интерактивной desktop-сессии пользователя. Не используйте Windows service, заблокированный RDP-сеанс или неинтерактивный background-runner для UI automation.
12
+
13
+ ## 2. Установите пакет
14
+
15
+ Linux:
16
+
17
+ ```bash
18
+ pipx install 'answer42[screenshot,linux-window-control]'
19
+ ```
20
+
21
+ Если `pipx` недоступен:
22
+
23
+ ```bash
24
+ python3 -m venv .venv
25
+ . .venv/bin/activate
26
+ pip install 'answer42[screenshot,linux-window-control]'
27
+ ```
28
+
29
+ Windows:
30
+
31
+ ```powershell
32
+ pipx install "answer42[screenshot,windows-window-control]"
33
+ ```
34
+
35
+ Если `pipx` недоступен:
36
+
37
+ ```powershell
38
+ python -m venv .venv
39
+ .venv\Scripts\Activate.ps1
40
+ pip install "answer42[screenshot,windows-window-control]"
41
+ ```
42
+
43
+ Проверьте CLI:
44
+
45
+ ```bash
46
+ answer42 --help
47
+ ```
48
+
49
+ ## 3. Установите bundled skills
50
+
51
+ PyPI-пакет не должен сам записывать skills в профили агентов. После установки пакета выполните:
52
+
53
+ ```bash
54
+ answer42 install-skills
55
+ ```
56
+
57
+ По умолчанию устанавливаются `answer42` и `answer42-rag` в OpenClaw:
58
+
59
+ ```text
60
+ ~/.openclaw/workspace/skills
61
+ ```
62
+
63
+ Другие варианты:
64
+
65
+ ```bash
66
+ answer42 install-skills --agent openclaw
67
+ answer42 install-skills --agent claude
68
+ answer42 install-skills --agent codex
69
+ answer42 install-skills --agent opencode
70
+ answer42 install-skills --agent pi
71
+ answer42 install-skills --agent hermes
72
+ answer42 install-skills --agent all
73
+ answer42 install-skills --target-dir /path/to/skills
74
+ answer42 install-skills --list-agents
75
+ answer42 install-skills --dry-run --agent all
76
+ ```
77
+
78
+ Для агентов без стабильного стандарта skill-директорий presets best-effort. Если конкретная установка использует другой путь, укажите `--target-dir` явно.
79
+
80
+ ## 4. Подготовьте credentials
81
+
82
+ Не передавайте логин и пароль в каждом `start_session`, если можно сохранить их в локальном credentials-файле.
83
+
84
+ Рекомендуемый вариант — файл вне репозитория, доступный процессу MCP-сервера через `ONEC_MCP_CREDENTIALS_FILE`:
85
+
86
+ ```bash
87
+ export ONEC_MCP_CREDENTIALS_FILE=/secure/path/credentials.json
88
+ ```
89
+
90
+ Минимальный формат:
91
+
92
+ ```json
93
+ {
94
+ "entries": [
95
+ {
96
+ "url": "https://example.invalid/infobase",
97
+ "username": "<USERNAME>",
98
+ "password": "<PASSWORD>"
99
+ }
100
+ ]
101
+ }
102
+ ```
103
+
104
+ Рекомендуемые права на Linux:
105
+
106
+ ```bash
107
+ chmod 600 /secure/path/credentials.json
108
+ ```
109
+
110
+ Если MCP уже подключён, можно сохранить запись через tool:
111
+
112
+ ```text
113
+ credentials_save(
114
+ url="<TARGET_INFOBASE_URL>",
115
+ username="<USERNAME>",
116
+ password="<PASSWORD>"
117
+ )
118
+ ```
119
+
120
+ Для проверки используйте:
121
+
122
+ ```text
123
+ credentials_check(base_url="<TARGET_INFOBASE_URL>")
124
+ ```
125
+
126
+ `credentials_check`, `credentials_save` и `credentials_remove` не возвращают пароль наружу. `credentials_list` рекомендуется скрывать в агентском клиенте, чтобы агент не видел список URL-шаблонов.
127
+
128
+ ## 5. Зарегистрируйте MCP-сервер
129
+
130
+ ### OpenClaw
131
+
132
+ Минимальный пример:
133
+
134
+ ```bash
135
+ /mcp set Answer42={"command":"answer42","args":[],"env":{"ONEC_MCP_CREDENTIALS_FILE":"/secure/path/credentials.json"},"enabled":true,"requestTimeoutMs":120000,"connectionTimeoutMs":30000,"toolFilter":{"exclude":["credentials_list"]}}
136
+ ```
137
+
138
+ Если `answer42` установлен в virtualenv и не находится в `PATH`, укажите полный путь к executable, например:
139
+
140
+ ```json
141
+ {
142
+ "command": "/path/to/.venv/bin/answer42",
143
+ "args": [],
144
+ "env": {
145
+ "ONEC_MCP_CREDENTIALS_FILE": "/secure/path/credentials.json"
146
+ },
147
+ "enabled": true,
148
+ "requestTimeoutMs": 120000,
149
+ "connectionTimeoutMs": 30000,
150
+ "toolFilter": {
151
+ "exclude": ["credentials_list"]
152
+ }
153
+ }
154
+ ```
155
+
156
+ После изменения MCP config перезапустите или перезагрузите агентский runtime/gateway, если клиент не подхватил конфигурацию автоматически.
157
+
158
+ ## 6. Проверьте установку
159
+
160
+ Безопасные проверки:
161
+
162
+ ```bash
163
+ answer42 --help
164
+ answer42 install-skills --dry-run
165
+ ```
166
+
167
+ Через MCP tools:
168
+
169
+ ```text
170
+ credentials_check(base_url="<TARGET_INFOBASE_URL>")
171
+ session_status()
172
+ ```
173
+
174
+ Smoke-проверка живой базы:
175
+
176
+ ```text
177
+ start_session(
178
+ session_id="smoke",
179
+ base_url="<TARGET_INFOBASE_URL>",
180
+ idle_timeout_minutes=15
181
+ )
182
+ active_window(session_id="smoke")
183
+ stop_session(session_id="smoke")
184
+ ```
185
+
186
+ Если `start_session` сообщает, что передан пароль из звёздочек (`***`, `********`), значит агент получил плейсхолдер вместо реального пароля. Попросите пользователя сохранить корректные credentials или передать настоящий пароль безопасным способом.
187
+
188
+ ## 7. Правила работы после установки
189
+
190
+ - Работайте через first-class MCP tools Answer42, а не через X11/WinAPI-клики.
191
+ - Не запускайте обычный клиент 1С вместо `/TESTMANAGER`, если пользователь явно не просил диагностику вне Answer42.
192
+ - Для ссылочных полей предпочитайте стандартные формы выбора 1С.
193
+ - После действий проверяйте user messages, error info и фактическое состояние формы.
194
+ - Не заявляйте выполнение UI-задачи без реального состояния, скриншота или PDF evidence.
195
+ - Завершайте сессии через `stop_session`.
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "answer42"
3
- version = "0.2.0"
3
+ version = "0.2.1"
4
4
  description = "Answer42 — The Answer to Life, Universe, and 1C — UI Driver. MCP-powered 1C:Enterprise UI automation: click, fill, navigate, test, and introspect managed forms through the test-client API"
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.11"
@@ -0,0 +1,274 @@
1
+ #!/usr/bin/env python3
2
+ """Build a small static GitLab Pages site from project Markdown docs."""
3
+
4
+ from __future__ import annotations
5
+
6
+ import html
7
+ import re
8
+ import shutil
9
+ from pathlib import Path
10
+
11
+ ROOT = Path(__file__).resolve().parents[1]
12
+ PUBLIC = ROOT / "public"
13
+ DOCS = ROOT / "docs"
14
+
15
+ PAGES = [
16
+ ("index.html", ROOT / "README.md", "README"),
17
+ ("installation.html", DOCS / "installation.md", "Установка"),
18
+ ("agent-installation.html", DOCS / "agent-installation.md", "Инструкция для AI-агентов"),
19
+ ("architecture.html", DOCS / "architecture.md", "Архитектура"),
20
+ ]
21
+
22
+ NAV = [
23
+ ("index.html", "README"),
24
+ ("installation.html", "Установка"),
25
+ ("agent-installation.html", "AI-агентам"),
26
+ ("architecture.html", "Архитектура"),
27
+ ]
28
+
29
+ CSS = """
30
+ :root { color-scheme: light dark; --bg:#0b1020; --panel:#111936; --text:#eef3ff; --muted:#aebbe2; --link:#67e8f9; --line:#2b365f; --code:#080d1c; }
31
+ @media (prefers-color-scheme: light) { :root { --bg:#f7f9ff; --panel:#ffffff; --text:#111827; --muted:#526078; --link:#0369a1; --line:#dbe3f5; --code:#f1f5f9; } }
32
+ * { box-sizing: border-box; }
33
+ body { margin:0; font:16px/1.62 Inter, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; color:var(--text); background:radial-gradient(circle at 20% 0%, rgba(103,232,249,.18), transparent 28rem), var(--bg); }
34
+ .wrapper { max-width: 1120px; margin: 0 auto; padding: 28px 20px 56px; }
35
+ .header { display:flex; align-items:center; gap:18px; padding:22px 0 18px; }
36
+ .logo { width:72px; height:72px; object-fit:contain; filter: drop-shadow(0 12px 24px rgba(0,0,0,.28)); }
37
+ .kicker { color:var(--muted); margin:0; }
38
+ h1,h2,h3,h4 { line-height:1.22; margin:1.6em 0 .55em; }
39
+ h1 { font-size: clamp(2rem, 5vw, 3.8rem); margin-top:.2em; letter-spacing:-.04em; }
40
+ h2 { font-size:1.8rem; border-top:1px solid var(--line); padding-top:1.1em; }
41
+ a { color:var(--link); text-decoration:none; } a:hover { text-decoration:underline; }
42
+ .nav { display:flex; flex-wrap:wrap; gap:10px; margin: 12px 0 28px; }
43
+ .nav a { padding:8px 12px; border:1px solid var(--line); border-radius:999px; background:rgba(255,255,255,.04); }
44
+ main { background: color-mix(in srgb, var(--panel) 92%, transparent); border:1px solid var(--line); border-radius:28px; padding: clamp(20px, 4vw, 44px); box-shadow:0 24px 80px rgba(0,0,0,.22); }
45
+ pre { overflow:auto; padding:16px 18px; background:var(--code); border:1px solid var(--line); border-radius:16px; }
46
+ code { font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; font-size:.92em; background:var(--code); border-radius:6px; padding:.12em .32em; }
47
+ pre code { background:transparent; padding:0; }
48
+ blockquote { margin:1.2em 0; padding:.5em 1.2em; border-left:4px solid var(--link); color:var(--muted); }
49
+ table { width:100%; border-collapse:collapse; margin:1.2em 0; display:block; overflow:auto; }
50
+ th,td { border:1px solid var(--line); padding:9px 11px; vertical-align:top; }
51
+ th { background:rgba(255,255,255,.07); }
52
+ img { max-width:100%; height:auto; }
53
+ .footer { color:var(--muted); margin-top:22px; font-size:.92rem; }
54
+ """.strip()
55
+
56
+
57
+ def slugify(text: str) -> str:
58
+ text = text.strip().lower()
59
+ text = re.sub(r"[^0-9a-zа-яё_-]+", "-", text, flags=re.I)
60
+ return text.strip("-") or "section"
61
+
62
+
63
+ def convert_inline(text: str) -> str:
64
+ placeholders: list[str] = []
65
+
66
+ def stash(value: str) -> str:
67
+ placeholders.append(value)
68
+ return f"\u0000{len(placeholders) - 1}\u0000"
69
+
70
+ text = html.escape(text)
71
+ text = re.sub(r"`([^`]+)`", lambda m: stash(f"<code>{m.group(1)}</code>"), text)
72
+ text = re.sub(r"!\[([^\]]*)\]\(([^)]+)\)", lambda m: stash(f'<img src="{html.escape(m.group(2), quote=True)}" alt="{html.escape(m.group(1), quote=True)}">'), text)
73
+ text = re.sub(r"\[([^\]]+)\]\(([^)]+)\)", lambda m: stash(f'<a href="{html.escape(m.group(2), quote=True)}">{m.group(1)}</a>'), text)
74
+ text = re.sub(r"\*\*([^*]+)\*\*", r"<strong>\1</strong>", text)
75
+ text = re.sub(r"(?<!\*)\*([^*]+)\*(?!\*)", r"<em>\1</em>", text)
76
+ # Some placeholders can be nested, for example Markdown links with code-formatted text:
77
+ # [`file.md`](file.md). Resolve repeatedly until no marker remains.
78
+ for _ in range(3):
79
+ changed = False
80
+ for i, value in enumerate(placeholders):
81
+ marker = f"\u0000{i}\u0000"
82
+ if marker in text:
83
+ text = text.replace(marker, value)
84
+ changed = True
85
+ if not changed:
86
+ break
87
+ return text
88
+
89
+
90
+ def markdown_to_html(markdown: str) -> str:
91
+ lines = markdown.splitlines()
92
+ out: list[str] = []
93
+ in_code = False
94
+ code_lang = ""
95
+ para: list[str] = []
96
+ in_ul = False
97
+ in_ol = False
98
+ in_quote = False
99
+ table: list[str] = []
100
+
101
+ def flush_para() -> None:
102
+ nonlocal para
103
+ if para:
104
+ out.append(f"<p>{convert_inline(' '.join(para))}</p>")
105
+ para = []
106
+
107
+ def flush_lists() -> None:
108
+ nonlocal in_ul, in_ol
109
+ if in_ul:
110
+ out.append("</ul>")
111
+ in_ul = False
112
+ if in_ol:
113
+ out.append("</ol>")
114
+ in_ol = False
115
+
116
+ def flush_quote() -> None:
117
+ nonlocal in_quote
118
+ if in_quote:
119
+ out.append("</blockquote>")
120
+ in_quote = False
121
+
122
+ def flush_table() -> None:
123
+ nonlocal table
124
+ if not table:
125
+ return
126
+ rows = []
127
+ for row in table:
128
+ cells = [c.strip() for c in row.strip().strip("|").split("|")]
129
+ rows.append(cells)
130
+ if len(rows) >= 2 and all(re.fullmatch(r":?-{3,}:?", c) for c in rows[1]):
131
+ header = rows[0]
132
+ body = rows[2:]
133
+ html_rows = ["<table><thead><tr>" + "".join(f"<th>{convert_inline(c)}</th>" for c in header) + "</tr></thead><tbody>"]
134
+ for r in body:
135
+ html_rows.append("<tr>" + "".join(f"<td>{convert_inline(c)}</td>" for c in r) + "</tr>")
136
+ html_rows.append("</tbody></table>")
137
+ out.append("".join(html_rows))
138
+ else:
139
+ for row in table:
140
+ out.append(f"<p>{convert_inline(row)}</p>")
141
+ table = []
142
+
143
+ for line in lines:
144
+ if line.startswith("```"):
145
+ flush_para()
146
+ flush_lists()
147
+ flush_quote()
148
+ flush_table()
149
+ if not in_code:
150
+ in_code = True
151
+ code_lang = line[3:].strip()
152
+ cls = f' class="language-{html.escape(code_lang, quote=True)}"' if code_lang else ""
153
+ out.append(f"<pre><code{cls}>")
154
+ else:
155
+ out.append("</code></pre>")
156
+ in_code = False
157
+ continue
158
+ if in_code:
159
+ out.append(html.escape(line) + "\n")
160
+ continue
161
+ if line.strip().startswith("|") and line.strip().endswith("|"):
162
+ flush_para()
163
+ flush_lists()
164
+ flush_quote()
165
+ table.append(line)
166
+ continue
167
+ else:
168
+ flush_table()
169
+ if not line.strip():
170
+ flush_para()
171
+ flush_lists()
172
+ flush_quote()
173
+ continue
174
+ m = re.match(r"^(#{1,6})\s+(.+)$", line)
175
+ if m:
176
+ flush_para()
177
+ flush_lists()
178
+ flush_quote()
179
+ level = len(m.group(1))
180
+ title = m.group(2).strip()
181
+ out.append(f'<h{level} id="{slugify(title)}">{convert_inline(title)}</h{level}>' )
182
+ continue
183
+ if line.startswith(">"):
184
+ flush_para()
185
+ flush_lists()
186
+ if not in_quote:
187
+ out.append("<blockquote>")
188
+ in_quote = True
189
+ out.append(f"<p>{convert_inline(line.lstrip('> ').strip())}</p>")
190
+ continue
191
+ m = re.match(r"^\s*[-*]\s+(.+)$", line)
192
+ if m:
193
+ flush_para()
194
+ flush_quote()
195
+ if not in_ul:
196
+ flush_lists()
197
+ out.append("<ul>")
198
+ in_ul = True
199
+ out.append(f"<li>{convert_inline(m.group(1))}</li>")
200
+ continue
201
+ m = re.match(r"^\s*\d+\.\s+(.+)$", line)
202
+ if m:
203
+ flush_para()
204
+ flush_quote()
205
+ if not in_ol:
206
+ flush_lists()
207
+ out.append("<ol>")
208
+ in_ol = True
209
+ out.append(f"<li>{convert_inline(m.group(1))}</li>")
210
+ continue
211
+ flush_lists()
212
+ flush_quote()
213
+ para.append(line.strip())
214
+
215
+ flush_para()
216
+ flush_lists()
217
+ flush_quote()
218
+ flush_table()
219
+ if in_code:
220
+ out.append("</code></pre>")
221
+ return "\n".join(out)
222
+
223
+
224
+ def page_html(title: str, body: str) -> str:
225
+ nav = "".join(f'<a href="{href}">{label}</a>' for href, label in NAV)
226
+ return f"""<!doctype html>
227
+ <html lang="ru">
228
+ <head>
229
+ <meta charset="utf-8">
230
+ <meta name="viewport" content="width=device-width, initial-scale=1">
231
+ <title>{html.escape(title)} — Answer42</title>
232
+ <link rel="icon" href="assets/answer42-logo.png">
233
+ <style>{CSS}</style>
234
+ </head>
235
+ <body>
236
+ <div class="wrapper">
237
+ <header class="header">
238
+ <img class="logo" src="assets/answer42-logo.png" alt="Answer42 logo">
239
+ <div>
240
+ <p class="kicker">The Answer to Life, Universe, and 1C — UI Driver</p>
241
+ <h1>Answer42</h1>
242
+ </div>
243
+ </header>
244
+ <nav class="nav">{nav}</nav>
245
+ <main>{body}</main>
246
+ <div class="footer">Generated from repository Markdown by <code>scripts/build_pages.py</code>.</div>
247
+ </div>
248
+ </body>
249
+ </html>
250
+ """
251
+
252
+
253
+ def main() -> None:
254
+ if PUBLIC.exists():
255
+ shutil.rmtree(PUBLIC)
256
+ (PUBLIC / "assets").mkdir(parents=True)
257
+ shutil.copy2(DOCS / "assets" / "answer42-logo.png", PUBLIC / "assets" / "answer42-logo.png")
258
+ for out_name, src, title in PAGES:
259
+ if not src.exists():
260
+ continue
261
+ markdown = src.read_text(encoding="utf-8")
262
+ markdown = re.sub(
263
+ r'<p\s+align="center">\s*<img\s+src="([^"]+)"\s+alt="([^"]*)"\s+width="([^"]+)"\s*>\s*</p>',
264
+ r'![\2](\1)',
265
+ markdown,
266
+ flags=re.S,
267
+ )
268
+ body = markdown_to_html(markdown)
269
+ (PUBLIC / out_name).write_text(page_html(title, body), encoding="utf-8")
270
+ print(f"Built {len(list(PUBLIC.glob('*.html')))} pages into {PUBLIC}")
271
+
272
+
273
+ if __name__ == "__main__":
274
+ main()
@@ -1,103 +0,0 @@
1
- # Публикация Answer42 в PyPI
2
-
3
- MCP Registry удалён из workflow. Единственный автоматизированный канал публикации — PyPI.
4
-
5
- ## GitLab CI
6
-
7
- `.gitlab-ci.yml` содержит pipeline:
8
-
9
- - `unit_tests` — `py_compile`, targeted `pytest`, `ruff`.
10
- - `package` — проверяет наличие packaged CF artifacts и packaged skills, собирает wheel/sdist и запускает `twine check`.
11
- - `publish_pypi` — на tag `vX.Y.Z` публикует `dist/*` в PyPI через `PYPI_API_TOKEN`.
12
-
13
- ## Required CI variables
14
-
15
- - `PYPI_API_TOKEN` — project-scoped PyPI token.
16
-
17
- ## Release checklist
18
-
19
- ```bash
20
- # 1. rebuild packaged CF artifacts from XML sources
21
- ONEC_PLATFORM_DIR=/opt/1cv8/x86_64/8.5.1.1150 python scripts/build_cf.py src/cf build/MCPTestManager.cf
22
- ONEC_PLATFORM_DIR=/opt/1cv8/x86_64/8.5.1.1150 python scripts/build_cf.py src/client_cf build/MCPTestClient.cf
23
- cp build/MCPTestManager.cf src/mcp_1c/assets/MCPTestManager.cf
24
- cp build/MCPTestClient.cf src/mcp_1c/assets/MCPTestClient.cf
25
-
26
- # 2. refresh packaged skills if skill sources changed
27
- rm -rf src/mcp_1c/assets/skills
28
- mkdir -p src/mcp_1c/assets/skills
29
- cp -R skills/answer42 src/mcp_1c/assets/skills/
30
- cp -R skills/answer42-rag src/mcp_1c/assets/skills/
31
-
32
- # 3. test/package locally
33
- python -m py_compile src/mcp_1c/*.py src/mcp_1c/rag/*.py
34
- PYTHONPATH=src python -m pytest -q -c /dev/null tests/test_credentials.py tests/test_protocol.py tests/test_server_tools.py
35
- python -m ruff check src/mcp_1c tests
36
- python -m build
37
- python -m twine check dist/*
38
-
39
- # 4. update version in pyproject.toml, commit, tag
40
- git tag v0.2.0
41
- git push origin beta v0.2.0
42
- ```
43
-
44
- ## Installing packaged skills
45
-
46
- PyPI packages do not automatically install agent skills into user profile directories. After installing Answer42, run:
47
-
48
- ```bash
49
- answer42 install-skills
50
- ```
51
-
52
- Default target is OpenClaw:
53
-
54
- ```text
55
- ~/.openclaw/workspace/skills
56
- ```
57
-
58
- Supported presets:
59
-
60
- ```bash
61
- answer42 install-skills --list-agents
62
- answer42 install-skills --agent openclaw
63
- answer42 install-skills --agent claude
64
- answer42 install-skills --agent codex
65
- answer42 install-skills --agent opencode
66
- answer42 install-skills --agent pi
67
- answer42 install-skills --agent hermes
68
- answer42 install-skills --agent all
69
- answer42 install-skills --target-dir /custom/skills/dir
70
- ```
71
-
72
- For agents without a stable public skill directory standard, presets are best-effort; use `--target-dir` for exact local setups.
73
-
74
- ## One-command release helper
75
-
76
- To bump `pyproject.toml`, create a release commit, create an annotated git tag and push both branch + tag:
77
-
78
- ```bash
79
- answer42 release 0.2.1
80
- ```
81
-
82
- This runs the equivalent of:
83
-
84
- ```bash
85
- # edit pyproject.toml: version = "0.2.1"
86
- git add pyproject.toml
87
- git commit -m "Release v0.2.1"
88
- git tag -a v0.2.1 -m "Release v0.2.1"
89
- git push origin HEAD
90
- git push origin v0.2.1
91
- ```
92
-
93
- The pushed tag triggers GitLab CI. CI publishes the PyPI package and creates a GitLab Release.
94
-
95
- Useful options:
96
-
97
- ```bash
98
- answer42 release 0.2.1 --dry-run
99
- answer42 release 0.2.1 --no-push
100
- answer42 release 0.2.1 --remote origin --branch beta
101
- ```
102
-
103
- The helper requires a clean working tree by default. Commit/stash other work first; `--allow-dirty` exists for exceptional cases.
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