elemctl 0.45.0__tar.gz → 0.47.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.
Files changed (84) hide show
  1. {elemctl-0.45.0 → elemctl-0.47.0}/PKG-INFO +25 -10
  2. {elemctl-0.45.0 → elemctl-0.47.0}/README.md +24 -9
  3. {elemctl-0.45.0 → elemctl-0.47.0}/docs/SPEC.md +141 -66
  4. {elemctl-0.45.0 → elemctl-0.47.0}/docs/SPEC.ru.md +470 -119
  5. {elemctl-0.45.0 → elemctl-0.47.0}/docs/changelog.md +129 -0
  6. {elemctl-0.45.0 → elemctl-0.47.0}/docs/changelog.ru.md +129 -0
  7. {elemctl-0.45.0 → elemctl-0.47.0}/docs/cli.md +74 -9
  8. {elemctl-0.45.0 → elemctl-0.47.0}/docs/cli.ru.md +74 -9
  9. {elemctl-0.45.0 → elemctl-0.47.0}/docs/config.md +9 -2
  10. {elemctl-0.45.0 → elemctl-0.47.0}/docs/config.ru.md +8 -2
  11. {elemctl-0.45.0 → elemctl-0.47.0}/docs/index.md +5 -3
  12. {elemctl-0.45.0 → elemctl-0.47.0}/docs/index.ru.md +17 -7
  13. {elemctl-0.45.0 → elemctl-0.47.0}/docs/mcp.md +10 -4
  14. {elemctl-0.45.0 → elemctl-0.47.0}/docs/mcp.ru.md +21 -6
  15. {elemctl-0.45.0 → elemctl-0.47.0}/docs/platform.md +25 -16
  16. {elemctl-0.45.0 → elemctl-0.47.0}/docs/platform.ru.md +197 -43
  17. {elemctl-0.45.0 → elemctl-0.47.0}/src/elemctl/__init__.py +1 -1
  18. {elemctl-0.45.0 → elemctl-0.47.0}/src/elemctl/build.py +38 -9
  19. {elemctl-0.45.0 → elemctl-0.47.0}/src/elemctl/cli.py +226 -43
  20. {elemctl-0.45.0 → elemctl-0.47.0}/src/elemctl/client.py +633 -33
  21. elemctl-0.47.0/src/elemctl/deploy.py +1076 -0
  22. {elemctl-0.45.0 → elemctl-0.47.0}/src/elemctl/errors.py +9 -0
  23. {elemctl-0.45.0 → elemctl-0.47.0}/src/elemctl/i18n.py +452 -67
  24. {elemctl-0.45.0 → elemctl-0.47.0}/src/elemctl/mcp_server.py +125 -13
  25. {elemctl-0.45.0 → elemctl-0.47.0}/src/elemctl/plugins.py +84 -1
  26. {elemctl-0.45.0 → elemctl-0.47.0}/src/elemctl/probe.py +103 -32
  27. elemctl-0.47.0/src/elemctl/registry.py +307 -0
  28. {elemctl-0.45.0 → elemctl-0.47.0}/src/elemctl/schema.py +121 -14
  29. {elemctl-0.45.0 → elemctl-0.47.0}/src/elemctl/transport.py +15 -4
  30. {elemctl-0.45.0 → elemctl-0.47.0}/src/elemctl/versions.py +84 -2
  31. {elemctl-0.45.0 → elemctl-0.47.0}/src/elemctl.egg-info/PKG-INFO +25 -10
  32. {elemctl-0.45.0 → elemctl-0.47.0}/src/elemctl.egg-info/SOURCES.txt +5 -0
  33. elemctl-0.47.0/tests/test_app_export.py +227 -0
  34. elemctl-0.47.0/tests/test_app_users.py +104 -0
  35. {elemctl-0.45.0 → elemctl-0.47.0}/tests/test_build.py +119 -0
  36. {elemctl-0.45.0 → elemctl-0.47.0}/tests/test_cli.py +127 -0
  37. {elemctl-0.45.0 → elemctl-0.47.0}/tests/test_client.py +231 -1
  38. {elemctl-0.45.0 → elemctl-0.47.0}/tests/test_deploy.py +439 -3
  39. elemctl-0.47.0/tests/test_extension_export.py +340 -0
  40. elemctl-0.47.0/tests/test_extension_verify.py +429 -0
  41. {elemctl-0.45.0 → elemctl-0.47.0}/tests/test_i18n.py +4 -1
  42. {elemctl-0.45.0 → elemctl-0.47.0}/tests/test_mcp.py +100 -1
  43. elemctl-0.47.0/tests/test_numbering_jumps.py +314 -0
  44. {elemctl-0.45.0 → elemctl-0.47.0}/tests/test_plugins.py +234 -0
  45. {elemctl-0.45.0 → elemctl-0.47.0}/tests/test_probe.py +2 -2
  46. {elemctl-0.45.0 → elemctl-0.47.0}/tests/test_probe_cleanup.py +109 -0
  47. {elemctl-0.45.0 → elemctl-0.47.0}/tests/test_registry.py +148 -0
  48. {elemctl-0.45.0 → elemctl-0.47.0}/tests/test_schema.py +189 -0
  49. {elemctl-0.45.0 → elemctl-0.47.0}/tests/test_transport.py +19 -2
  50. {elemctl-0.45.0 → elemctl-0.47.0}/tests/test_versions.py +22 -0
  51. elemctl-0.45.0/src/elemctl/deploy.py +0 -618
  52. elemctl-0.45.0/src/elemctl/registry.py +0 -161
  53. {elemctl-0.45.0 → elemctl-0.47.0}/.env.example +0 -0
  54. {elemctl-0.45.0 → elemctl-0.47.0}/LICENSE +0 -0
  55. {elemctl-0.45.0 → elemctl-0.47.0}/MANIFEST.in +0 -0
  56. {elemctl-0.45.0 → elemctl-0.47.0}/NOTICE +0 -0
  57. {elemctl-0.45.0 → elemctl-0.47.0}/ORIGIN.md +0 -0
  58. {elemctl-0.45.0 → elemctl-0.47.0}/docs/library.md +0 -0
  59. {elemctl-0.45.0 → elemctl-0.47.0}/docs/library.ru.md +0 -0
  60. {elemctl-0.45.0 → elemctl-0.47.0}/pyproject.toml +0 -0
  61. {elemctl-0.45.0 → elemctl-0.47.0}/setup.cfg +0 -0
  62. {elemctl-0.45.0 → elemctl-0.47.0}/src/elemctl/__main__.py +0 -0
  63. {elemctl-0.45.0 → elemctl-0.47.0}/src/elemctl/auth.py +0 -0
  64. {elemctl-0.45.0 → elemctl-0.47.0}/src/elemctl/config.py +0 -0
  65. {elemctl-0.45.0 → elemctl-0.47.0}/src/elemctl/selfupdate.py +0 -0
  66. {elemctl-0.45.0 → elemctl-0.47.0}/src/elemctl.egg-info/dependency_links.txt +0 -0
  67. {elemctl-0.45.0 → elemctl-0.47.0}/src/elemctl.egg-info/entry_points.txt +0 -0
  68. {elemctl-0.45.0 → elemctl-0.47.0}/src/elemctl.egg-info/requires.txt +0 -0
  69. {elemctl-0.45.0 → elemctl-0.47.0}/src/elemctl.egg-info/top_level.txt +0 -0
  70. {elemctl-0.45.0 → elemctl-0.47.0}/tests/test_changelog_link.py +0 -0
  71. {elemctl-0.45.0 → elemctl-0.47.0}/tests/test_cli_docs.py +0 -0
  72. {elemctl-0.45.0 → elemctl-0.47.0}/tests/test_config.py +0 -0
  73. {elemctl-0.45.0 → elemctl-0.47.0}/tests/test_conventions.py +0 -0
  74. {elemctl-0.45.0 → elemctl-0.47.0}/tests/test_docs_coverage.py +0 -0
  75. {elemctl-0.45.0 → elemctl-0.47.0}/tests/test_extract_adapter.py +0 -0
  76. {elemctl-0.45.0 → elemctl-0.47.0}/tests/test_rebuild_docs.py +0 -0
  77. {elemctl-0.45.0 → elemctl-0.47.0}/tests/test_release_notes.py +0 -0
  78. {elemctl-0.45.0 → elemctl-0.47.0}/tests/test_selfupdate.py +0 -0
  79. {elemctl-0.45.0 → elemctl-0.47.0}/tests/test_selfupdate_latest.py +0 -0
  80. {elemctl-0.45.0 → elemctl-0.47.0}/tests/test_server_starting.py +0 -0
  81. {elemctl-0.45.0 → elemctl-0.47.0}/tests/test_token_access.py +0 -0
  82. {elemctl-0.45.0 → elemctl-0.47.0}/tests/test_user_lists.py +0 -0
  83. {elemctl-0.45.0 → elemctl-0.47.0}/tests/test_workflows.py +0 -0
  84. {elemctl-0.45.0 → elemctl-0.47.0}/tools/extract_adapter.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: elemctl
3
- Version: 0.45.0
3
+ Version: 0.47.0
4
4
  Summary: CLI, MCP server and library for the Console API v2 of the 1C:Enterprise.Element platform (1cmycloud): applications, builds from source, one-command deploy with verification, a compile probe
5
5
  Author: KeyFire
6
6
  License-Expression: MIT
@@ -52,9 +52,9 @@ Development notes and updates (in Russian): the [1C × AI: engineering workshop]
52
52
 
53
53
  <!-- features:start -->
54
54
 
55
- - **Applications**: list, details, create, start, stop, delete, technology version, debug-session data (`apps debug`). The list filters by name on the client side and prints short cards with `--brief`. Commands that address one application take its id or its exact name.
55
+ - **Applications**: list, details, create, start, stop, delete, technology version, debug-session data (`apps debug`), the build the application runs and the build of an applied extension saved to a file (`apps export`, and `apps export-extension` from Console API 2.1). The list filters by name on the client side and prints short cards with `--brief`. Commands that address one application take its id or its exact name.
56
56
  - **Projects and builds**: upload `.xasm`/`.xlib`, list builds, delete.
57
- - **Build from sources**: elemctl packs a project directory into a build archive with a manifest and git metadata. That directory holds `Проект.yaml` and the modules. The version comes from the flag, from the last build's counter or from the CI run number in the environment: `CI_PIPELINE_IID`, `GITHUB_RUN_NUMBER`, `BUILD_NUMBER`. The output carries it as a field. Descriptors written with English key spellings `Name`/`Vendor`/`Version` are read as well as Russian ones.
57
+ - **Build from sources**: elemctl packs a project directory into a build archive with a manifest and git metadata. That directory holds `Проект.yaml` and the modules. The version comes from the flag, from the last build's counter or from the CI run number in the environment: `CI_PIPELINE_IID`, `GITHUB_RUN_NUMBER`, `BUILD_NUMBER`. The output carries it as a field. Descriptors written with English key spellings `Name`/`Vendor`/`Version` are read as well as Russian ones. An application, a library and an extension are built alike, and an extension gets the manifest version the server reads extensions by.
58
58
  - **One-command deploy**: build -> upload -> apply -> restart -> **verification that the apply actually took effect**. Uncommitted changes in the project directory show up in the report as `dirty`. Pass `--require-clean` to stop on a dirty tree.
59
59
  - **Compilation check without risking the application** (`elemctl probe`): the server compiles the sources, and it does so inside a throwaway application. Errors come back with file, line and column, and the probe deletes what it created. The working application stays out of reach on purpose: the probe never reads `ELEMENT_APP_ID` or `ELEMENT_PROJECT_ID`.
60
60
  - **User lists** (`elemctl user-lists`): the sign-in settings you would normally open the control panel for, namely self-registration and signing in with a login and a password. Address a list by id, by presentation or by the application that owns it.
@@ -205,8 +205,8 @@ for the cache to watch and stays pinned for the life of the process instead.
205
205
 
206
206
  ### Behaviour of the tool
207
207
 
208
- `ELEMCTL_LANG`, `ELEMCTL_NO_PLUGINS` and `ELEMCTL_DATA_DIR` are set through the
209
- environment only – a connection `.env` is not their place. `ELEMCTL_NO_PROXY` is the
208
+ `ELEMCTL_LANG`, `ELEMCTL_NO_PLUGINS`, `ELEMCTL_DATA_DIR` and `ELEMCTL_REGISTRY_LIMIT` are
209
+ set through the environment only – a connection `.env` is not their place. `ELEMCTL_NO_PROXY` is the
210
210
  exception, explained above: it reads the same file the connection does.
211
211
 
212
212
  | Variable | Purpose |
@@ -215,6 +215,7 @@ exception, explained above: it reads the same file the connection does.
215
215
  | `ELEMCTL_NO_PROXY` | set it to bypass the environment's proxy for every call (loopback and private addresses are bypassed anyway); also readable from the stand's `.env` |
216
216
  | `ELEMCTL_NO_PLUGINS` | do not look for plugins: work with the core capabilities only |
217
217
  | `ELEMCTL_DATA_DIR` | the directory of the local registry of uploads; by default `%LOCALAPPDATA%\elemctl` on Windows and `$XDG_STATE_HOME/elemctl` (`~/.local/state/elemctl`) elsewhere |
218
+ | `ELEMCTL_REGISTRY_LIMIT` | how many uploads the local registry of uploads keeps; 1000 by default, `0` keeps every one |
218
219
 
219
220
  ### The local registry of uploads
220
221
 
@@ -231,6 +232,12 @@ had no registry yet is not in it, and there the listings show what the platform
231
232
  registry that cannot be written is a warning and never a failed upload; the file can be
232
233
  deleted at any time, and only the history of this machine goes with it.
233
234
 
235
+ The registry keeps the newest thousand uploads, `ELEMCTL_REGISTRY_LIMIT` sets another
236
+ number, and `0` keeps every one. Once the file has grown past the limit by a tenth, the next
237
+ upload cuts it back to the newest lines: the builds a project still lists and an application
238
+ still runs are the recent ones, and a file nobody trimmed grew for as long as the machine
239
+ deployed.
240
+
234
241
  ### The CI environment
235
242
 
236
243
  The build declares no variables of its own, but it reads the ones CI sets itself. That is why a pipeline needs neither a version flag nor an edit of the sources:
@@ -291,6 +298,8 @@ Every command writes JSON to stdout, and progress of long-running operations goe
291
298
 
292
299
  The `--json` flag turns that convention into a guarantee a script can lean on, and it is accepted in any position. While the command runs, stdout is swapped for stderr, so the real stdout receives nothing but the JSON answer. No stray line from a plugin or a library can slip in. Parse the stream whole with `json.load` instead of hunting for the first brace. With `--json` a failure also goes to stderr and stdout stays empty, because a pipeline would read anything in the machine channel as the answer.
293
300
 
301
+ `--quiet`, also accepted in any position, silences the progress stream instead: neither progress lines nor warnings reach stderr, and the answer and a failure stay where they were. The form of the answer does not change with it. A caller who merges the two streams into one gets the answer alone.
302
+
294
303
  `apps list` and `builds list` print their count and truncation notes after the answer. Other stderr output can still come first, so only stdout read on its own is safe to parse whole.
295
304
 
296
305
  For the full list of commands run `elemctl --help`, and for one group `elemctl apps --help`, `elemctl deploy --help` and so on.
@@ -338,14 +347,17 @@ The server reads connection credentials from the same `ELEMENT_*` variables / `.
338
347
  | `get_build` | the whole card of one build; `version` is the build's version (`1.0-42`), an id is accepted too and resolved through the listing |
339
348
  | `build_assembly` | build a `.xasm`/`.xlib` archive from the sources locally (does not talk to the platform) |
340
349
  | `inspect_assembly` | parse a built archive: manifest, project properties, subsystems and global types with qualified names (local) |
341
- | `deploy` | the whole cycle from sources, with a check that the build was applied; the verdict is `ok`, the details are `problems` and `log`; a server that is still starting is waited out for up to `server_start_timeout` seconds (900 by default) |
350
+ | `deploy` | the whole cycle from sources, with a check that the build was applied; the verdict is `ok`, the details are `problems` and `log`; `allow_data_loss` lets through what the schema guard refuses (a narrowing, a type change, a catalog removed whole), like `--allow-data-loss`; a server that is still starting is waited out for up to `server_start_timeout` seconds (900 by default) |
342
351
  | `probe` | check the compilation with the server compiler without touching the working application; errors with file, line and column, cleans up after itself; with `keep` the report names the command that removes what was left |
343
- | `probe_cleanup` | remove a probe left on the stand, by `keep` or by a cleanup that broke off, starting from its application: the application, the probe build in the probe's own project and the project when nothing is left in it; an application that is not a probe's is refused |
352
+ | `probe_cleanup` | remove a probe left on the stand, by `keep` or by a cleanup that broke off, starting from its application: the application, the probe build in the probe's own project and the project when nothing is left in it; a probe given its own name and version is known by the local registry of uploads, which also names the builds deployed into it; an application that is not a probe's is refused |
344
353
  | `apply_build` | apply an uploaded build to the application by its id |
345
- | `verify_deploy` | verify the apply actually took effect: failed tasks, the applied build, the availability of the uri |
354
+ | `verify_deploy` | verify the apply actually took effect: failed tasks, the applied build (an extension build by the extensions of the application), the availability of the uri |
346
355
  | `list_user_lists` | user lists; `name` filters by a substring of the presentation |
347
356
  | `configure_user_list` | self-registration and password sign-in; without the flags it only reports the current state |
357
+ | `list_app_users` | who is connected to an application: the user list, the id, the presentation, whether an administrator and whether token access is on, with a count line; the platform gives no login here |
348
358
  | `token_access` | a user's access to the HTTP services of an application by a token, the flag behind a 500 "Token access is denied"; shows it, and with `enabled` switches it and reads it back; an empty `user` means the account elemctl signs in with |
359
+ | `export_app` | save the build an application runs to a file; the extensions applied to it are not in the archive, `export_extension` saves them; an empty `output` is the current directory of the server and the name `{Name} {Version}.xasm`; a server without the method gets a plain refusal |
360
+ | `export_extension` | save the build of an extension applied to an application to a file (Console API 2.1); `extension` is the extension id, the id of its project, the name or the presentation of the project, and a miss names the extensions the application has; an empty `output` is the current directory of the server and the name `{Name} {Version}.xasm`; a server without the method gets a plain refusal |
349
361
  | `list_branches` | list of development-environment branches; the `project_id` and `name` filters are optional |
350
362
  | `merge_branch` | accept the changes of a development-environment branch |
351
363
  | `debug_adapter` | the path to the platform debug adapter from a plugin; a missing plugin is an answer (`found: false`), not an error (local) |
@@ -358,7 +370,7 @@ A single environment is not a limit. Every tool that talks to the platform takes
358
370
 
359
371
  `list_projects` behaves the same way and returns id, name, project kind, space, application count and the deletion flag. Its `name` filters by a substring too, and projects marked deleted stay hidden until `include_deleted` is set: a stand a few months old keeps hundreds of them against a handful of live ones.
360
372
 
361
- `list_builds` answers with brief cards as well, holding id, versions, date, branch and commit, and with the ten newest builds unless `limit` says otherwise; 0 lifts the cut. The cards sit under `builds`, next to the counters `total` (how many builds the platform returned) and `shown`, plus a `summary` line. The platform's listing is not the project's whole history: it deletes the builds nobody uses, whatever their age. The build an application runs, the project's first build and a release build stay. The listing has no pages, and `summary` says plainly that what you see is the remainder of the history. It reads that off the answer itself: the platform hands out the numbers of one base version consecutively, so a number missing from the listing is a build already taken away.
373
+ `list_builds` answers with brief cards as well, holding id, versions, date, branch and commit, and with the ten newest builds unless `limit` says otherwise; 0 lifts the cut. The cards sit under `builds`, next to the counters `total` (how many builds the platform returned) and `shown`, plus a `summary` line. The platform's listing is not the project's whole history: it deletes the builds nobody uses, whatever their age, whenever an application of the project finishes applying a build. The build an application runs, the build with the highest number of its base, a release build and a build uploaded without a project id stay, the project's first build among the last. The listing has no pages, and `summary` says plainly that what you see is the remainder of the history. It reads that off the answer itself: the platform hands out the numbers of one base version consecutively, so a number missing from the listing is a build already taken away – unless it is a jump. A build uploaded without a project id keeps the number of its archive and the count goes on from it, and a hole under such a build of this machine is named in `summary` as a jump rather than a deletion. A hole that holds an upload of this machine into the project is a jump and a deletion at once, and `summary` names both.
362
374
 
363
375
  `get_build` hands back one card whole and is addressed by the build version. An id is accepted as well and looked up in the listing, because the address the method understands is the version, not the id of a card.
364
376
 
@@ -395,6 +407,7 @@ from elemctl.plugins import Argument, Command
395
407
 
396
408
  def warm_up(context, stand="", force=False):
397
409
  context.log(f"warming up {stand}") # progress: stderr in the CLI, the log field in MCP
410
+ # context.surface says which of them called: "cli" or "mcp"
398
411
  card = context.client.get_app(stand) # the client is built on first use
399
412
  return {"ok": True, "status": card.get("status")}
400
413
 
@@ -409,6 +422,8 @@ def commands():
409
422
 
410
423
  The result of a handler has to be JSON-serializable: the CLI prints it, the MCP tool returns it. The exit code of the CLI comes from the result too. An integer from 0 to 255 in the `exit-code` field becomes the exit code as it is, so a command with three outcomes can hand a script "no differences", "differences" and "a step failed" as 0, 1 and 2. Without that field, a dict result with `"ok": false` ends with exit code 1, the same convention the `deploy` and `probe` reports follow. When the field disagrees with `ok`, the field decides. A string, `true` or 300 is not a code, and the CLI goes by `ok` instead. The MCP tool returns the field with the rest of the result. Do not call `sys.exit` in a handler to get a code: the same function runs inside the MCP server, where the call would never be answered and the server would stop. Argument types are `str`, `int`, `float` and `bool` for a flag. elemctl adds `env_file` to the MCP tool itself, so a plugin command reaches other environments exactly like the core tools do. A command may not take over a name the core already occupies: such a command is left out. So is a plugin that fails to load, one written for a newer core for instance. elemctl names them on stderr and in `elemctl plugins` and keeps working with the rest.
411
424
 
425
+ `context.surface` says where the call came from: `"cli"` for a subcommand and `"mcp"` for a tool. A command that answers differently on the two, briefly in a tool and in full on a terminal say, reads it there instead of guessing from how `context.log` behaves. On an older core the attribute is missing, so read it with `getattr(context, "surface", None)`.
426
+
412
427
  A positional argument may add a CLI-only key synonym: `Argument("page", cli_alias="--page")` accepts both `elemctl wiki-get 123` and `elemctl wiki-get --page 123`. The MCP tool schema keeps the one `page` parameter it always had – `cli_alias` only changes what the CLI parser accepts, nothing about the declared arguments themselves. The two forms are mutually exclusive: the parser refuses both at once, and refuses neither when the argument is required. A plugin that declares no `cli_alias` behaves exactly as before.
413
428
 
414
429
  ```bash
@@ -510,7 +525,7 @@ The project directory must follow the `{repo}/{vendor}/{name}/Проект.yaml`
510
525
  - On some platform configurations an application created from `--project-id` alone comes out as an empty skeleton with no project data. Create it from a build instead: `elemctl apps create <name> --project-id <id> --latest-build`. The `create_app` MCP tool substitutes the latest build for you. Run `elemctl deploy` once the application exists.
511
526
  - The platform describes an application created with an `Error` status only as "Неизвестная ошибка. Обратитесь к администратору". The details live in the application's task: the files, lines and columns of the compilation errors. `apps create --wait` and `apps ensure` print them after the generic text, the way `deploy` and `verify` have long done, so you do not have to dig through the server log. Not every failure ends in `Error`, though. A failed apply is rolled back to the previous build and the application comes up `Running`, so `--wait` also verifies which build the application really runs and answers with exit code 1 when it is not the one you asked for. Use `--no-verify` for the plain wait.
512
527
  - You cannot compile the sources without creating something on the platform: compilation belongs to the server and happens when a build is applied. That is what `probe` is for, and it takes the hit on a throwaway application instead of the working one. One probe costs as much time as creating an application does, so minutes. Its place is before a deploy or in CI, not in a per-keystroke loop.
513
- - A platform project is identified by the `Vendor` + `Name` pair in the manifest, not by the `Ид` in `Проект.yaml`. A build uploaded without a project id lands in the project that already owns the pair, and a second project for the same pair is refused with a 409.
528
+ - A platform project is identified by the `Ид` in `Проект.yaml`. A build uploaded without a project id lands in the project that carries its `Ид`, whatever the `Vendor` + `Name` pair of the manifest, and renames that project after itself. The pair only has to be free: a fresh `Ид` with a pair another project already holds is refused with a 409.
514
529
  - You sign in to a freshly created application with a control-panel account. The application gets its own empty user list, password sign-in is off and no account service is attached, so the accounts that work in other applications do not work here. Connecting another application's user list does not change that, and neither does enabling the local sign-in. `apps create` and `apps ensure` say so themselves: the `sign-in` field of the answer, and the same text on stderr.
515
530
  - Deleted applications stay in the platform's list with a `Deleted` status and their former `id`. On that id `apps get` and `deploy` return 404. `apps find` and `apps ensure` skip them; `apps find --include-deleted` brings the previous search behaviour back.
516
531
  - The platform refuses to delete an application that has unpublished changes in the development environment and answers HTTP 400 `FAILED_PRECONDITION`. The Console API has no forced deletion, so the control panel is the only way out. elemctl points this out in the error message.
@@ -20,9 +20,9 @@ Development notes and updates (in Russian): the [1C × AI: engineering workshop]
20
20
 
21
21
  <!-- features:start -->
22
22
 
23
- - **Applications**: list, details, create, start, stop, delete, technology version, debug-session data (`apps debug`). The list filters by name on the client side and prints short cards with `--brief`. Commands that address one application take its id or its exact name.
23
+ - **Applications**: list, details, create, start, stop, delete, technology version, debug-session data (`apps debug`), the build the application runs and the build of an applied extension saved to a file (`apps export`, and `apps export-extension` from Console API 2.1). The list filters by name on the client side and prints short cards with `--brief`. Commands that address one application take its id or its exact name.
24
24
  - **Projects and builds**: upload `.xasm`/`.xlib`, list builds, delete.
25
- - **Build from sources**: elemctl packs a project directory into a build archive with a manifest and git metadata. That directory holds `Проект.yaml` and the modules. The version comes from the flag, from the last build's counter or from the CI run number in the environment: `CI_PIPELINE_IID`, `GITHUB_RUN_NUMBER`, `BUILD_NUMBER`. The output carries it as a field. Descriptors written with English key spellings `Name`/`Vendor`/`Version` are read as well as Russian ones.
25
+ - **Build from sources**: elemctl packs a project directory into a build archive with a manifest and git metadata. That directory holds `Проект.yaml` and the modules. The version comes from the flag, from the last build's counter or from the CI run number in the environment: `CI_PIPELINE_IID`, `GITHUB_RUN_NUMBER`, `BUILD_NUMBER`. The output carries it as a field. Descriptors written with English key spellings `Name`/`Vendor`/`Version` are read as well as Russian ones. An application, a library and an extension are built alike, and an extension gets the manifest version the server reads extensions by.
26
26
  - **One-command deploy**: build -> upload -> apply -> restart -> **verification that the apply actually took effect**. Uncommitted changes in the project directory show up in the report as `dirty`. Pass `--require-clean` to stop on a dirty tree.
27
27
  - **Compilation check without risking the application** (`elemctl probe`): the server compiles the sources, and it does so inside a throwaway application. Errors come back with file, line and column, and the probe deletes what it created. The working application stays out of reach on purpose: the probe never reads `ELEMENT_APP_ID` or `ELEMENT_PROJECT_ID`.
28
28
  - **User lists** (`elemctl user-lists`): the sign-in settings you would normally open the control panel for, namely self-registration and signing in with a login and a password. Address a list by id, by presentation or by the application that owns it.
@@ -173,8 +173,8 @@ for the cache to watch and stays pinned for the life of the process instead.
173
173
 
174
174
  ### Behaviour of the tool
175
175
 
176
- `ELEMCTL_LANG`, `ELEMCTL_NO_PLUGINS` and `ELEMCTL_DATA_DIR` are set through the
177
- environment only – a connection `.env` is not their place. `ELEMCTL_NO_PROXY` is the
176
+ `ELEMCTL_LANG`, `ELEMCTL_NO_PLUGINS`, `ELEMCTL_DATA_DIR` and `ELEMCTL_REGISTRY_LIMIT` are
177
+ set through the environment only – a connection `.env` is not their place. `ELEMCTL_NO_PROXY` is the
178
178
  exception, explained above: it reads the same file the connection does.
179
179
 
180
180
  | Variable | Purpose |
@@ -183,6 +183,7 @@ exception, explained above: it reads the same file the connection does.
183
183
  | `ELEMCTL_NO_PROXY` | set it to bypass the environment's proxy for every call (loopback and private addresses are bypassed anyway); also readable from the stand's `.env` |
184
184
  | `ELEMCTL_NO_PLUGINS` | do not look for plugins: work with the core capabilities only |
185
185
  | `ELEMCTL_DATA_DIR` | the directory of the local registry of uploads; by default `%LOCALAPPDATA%\elemctl` on Windows and `$XDG_STATE_HOME/elemctl` (`~/.local/state/elemctl`) elsewhere |
186
+ | `ELEMCTL_REGISTRY_LIMIT` | how many uploads the local registry of uploads keeps; 1000 by default, `0` keeps every one |
186
187
 
187
188
  ### The local registry of uploads
188
189
 
@@ -199,6 +200,12 @@ had no registry yet is not in it, and there the listings show what the platform
199
200
  registry that cannot be written is a warning and never a failed upload; the file can be
200
201
  deleted at any time, and only the history of this machine goes with it.
201
202
 
203
+ The registry keeps the newest thousand uploads, `ELEMCTL_REGISTRY_LIMIT` sets another
204
+ number, and `0` keeps every one. Once the file has grown past the limit by a tenth, the next
205
+ upload cuts it back to the newest lines: the builds a project still lists and an application
206
+ still runs are the recent ones, and a file nobody trimmed grew for as long as the machine
207
+ deployed.
208
+
202
209
  ### The CI environment
203
210
 
204
211
  The build declares no variables of its own, but it reads the ones CI sets itself. That is why a pipeline needs neither a version flag nor an edit of the sources:
@@ -259,6 +266,8 @@ Every command writes JSON to stdout, and progress of long-running operations goe
259
266
 
260
267
  The `--json` flag turns that convention into a guarantee a script can lean on, and it is accepted in any position. While the command runs, stdout is swapped for stderr, so the real stdout receives nothing but the JSON answer. No stray line from a plugin or a library can slip in. Parse the stream whole with `json.load` instead of hunting for the first brace. With `--json` a failure also goes to stderr and stdout stays empty, because a pipeline would read anything in the machine channel as the answer.
261
268
 
269
+ `--quiet`, also accepted in any position, silences the progress stream instead: neither progress lines nor warnings reach stderr, and the answer and a failure stay where they were. The form of the answer does not change with it. A caller who merges the two streams into one gets the answer alone.
270
+
262
271
  `apps list` and `builds list` print their count and truncation notes after the answer. Other stderr output can still come first, so only stdout read on its own is safe to parse whole.
263
272
 
264
273
  For the full list of commands run `elemctl --help`, and for one group `elemctl apps --help`, `elemctl deploy --help` and so on.
@@ -306,14 +315,17 @@ The server reads connection credentials from the same `ELEMENT_*` variables / `.
306
315
  | `get_build` | the whole card of one build; `version` is the build's version (`1.0-42`), an id is accepted too and resolved through the listing |
307
316
  | `build_assembly` | build a `.xasm`/`.xlib` archive from the sources locally (does not talk to the platform) |
308
317
  | `inspect_assembly` | parse a built archive: manifest, project properties, subsystems and global types with qualified names (local) |
309
- | `deploy` | the whole cycle from sources, with a check that the build was applied; the verdict is `ok`, the details are `problems` and `log`; a server that is still starting is waited out for up to `server_start_timeout` seconds (900 by default) |
318
+ | `deploy` | the whole cycle from sources, with a check that the build was applied; the verdict is `ok`, the details are `problems` and `log`; `allow_data_loss` lets through what the schema guard refuses (a narrowing, a type change, a catalog removed whole), like `--allow-data-loss`; a server that is still starting is waited out for up to `server_start_timeout` seconds (900 by default) |
310
319
  | `probe` | check the compilation with the server compiler without touching the working application; errors with file, line and column, cleans up after itself; with `keep` the report names the command that removes what was left |
311
- | `probe_cleanup` | remove a probe left on the stand, by `keep` or by a cleanup that broke off, starting from its application: the application, the probe build in the probe's own project and the project when nothing is left in it; an application that is not a probe's is refused |
320
+ | `probe_cleanup` | remove a probe left on the stand, by `keep` or by a cleanup that broke off, starting from its application: the application, the probe build in the probe's own project and the project when nothing is left in it; a probe given its own name and version is known by the local registry of uploads, which also names the builds deployed into it; an application that is not a probe's is refused |
312
321
  | `apply_build` | apply an uploaded build to the application by its id |
313
- | `verify_deploy` | verify the apply actually took effect: failed tasks, the applied build, the availability of the uri |
322
+ | `verify_deploy` | verify the apply actually took effect: failed tasks, the applied build (an extension build by the extensions of the application), the availability of the uri |
314
323
  | `list_user_lists` | user lists; `name` filters by a substring of the presentation |
315
324
  | `configure_user_list` | self-registration and password sign-in; without the flags it only reports the current state |
325
+ | `list_app_users` | who is connected to an application: the user list, the id, the presentation, whether an administrator and whether token access is on, with a count line; the platform gives no login here |
316
326
  | `token_access` | a user's access to the HTTP services of an application by a token, the flag behind a 500 "Token access is denied"; shows it, and with `enabled` switches it and reads it back; an empty `user` means the account elemctl signs in with |
327
+ | `export_app` | save the build an application runs to a file; the extensions applied to it are not in the archive, `export_extension` saves them; an empty `output` is the current directory of the server and the name `{Name} {Version}.xasm`; a server without the method gets a plain refusal |
328
+ | `export_extension` | save the build of an extension applied to an application to a file (Console API 2.1); `extension` is the extension id, the id of its project, the name or the presentation of the project, and a miss names the extensions the application has; an empty `output` is the current directory of the server and the name `{Name} {Version}.xasm`; a server without the method gets a plain refusal |
317
329
  | `list_branches` | list of development-environment branches; the `project_id` and `name` filters are optional |
318
330
  | `merge_branch` | accept the changes of a development-environment branch |
319
331
  | `debug_adapter` | the path to the platform debug adapter from a plugin; a missing plugin is an answer (`found: false`), not an error (local) |
@@ -326,7 +338,7 @@ A single environment is not a limit. Every tool that talks to the platform takes
326
338
 
327
339
  `list_projects` behaves the same way and returns id, name, project kind, space, application count and the deletion flag. Its `name` filters by a substring too, and projects marked deleted stay hidden until `include_deleted` is set: a stand a few months old keeps hundreds of them against a handful of live ones.
328
340
 
329
- `list_builds` answers with brief cards as well, holding id, versions, date, branch and commit, and with the ten newest builds unless `limit` says otherwise; 0 lifts the cut. The cards sit under `builds`, next to the counters `total` (how many builds the platform returned) and `shown`, plus a `summary` line. The platform's listing is not the project's whole history: it deletes the builds nobody uses, whatever their age. The build an application runs, the project's first build and a release build stay. The listing has no pages, and `summary` says plainly that what you see is the remainder of the history. It reads that off the answer itself: the platform hands out the numbers of one base version consecutively, so a number missing from the listing is a build already taken away.
341
+ `list_builds` answers with brief cards as well, holding id, versions, date, branch and commit, and with the ten newest builds unless `limit` says otherwise; 0 lifts the cut. The cards sit under `builds`, next to the counters `total` (how many builds the platform returned) and `shown`, plus a `summary` line. The platform's listing is not the project's whole history: it deletes the builds nobody uses, whatever their age, whenever an application of the project finishes applying a build. The build an application runs, the build with the highest number of its base, a release build and a build uploaded without a project id stay, the project's first build among the last. The listing has no pages, and `summary` says plainly that what you see is the remainder of the history. It reads that off the answer itself: the platform hands out the numbers of one base version consecutively, so a number missing from the listing is a build already taken away – unless it is a jump. A build uploaded without a project id keeps the number of its archive and the count goes on from it, and a hole under such a build of this machine is named in `summary` as a jump rather than a deletion. A hole that holds an upload of this machine into the project is a jump and a deletion at once, and `summary` names both.
330
342
 
331
343
  `get_build` hands back one card whole and is addressed by the build version. An id is accepted as well and looked up in the listing, because the address the method understands is the version, not the id of a card.
332
344
 
@@ -363,6 +375,7 @@ from elemctl.plugins import Argument, Command
363
375
 
364
376
  def warm_up(context, stand="", force=False):
365
377
  context.log(f"warming up {stand}") # progress: stderr in the CLI, the log field in MCP
378
+ # context.surface says which of them called: "cli" or "mcp"
366
379
  card = context.client.get_app(stand) # the client is built on first use
367
380
  return {"ok": True, "status": card.get("status")}
368
381
 
@@ -377,6 +390,8 @@ def commands():
377
390
 
378
391
  The result of a handler has to be JSON-serializable: the CLI prints it, the MCP tool returns it. The exit code of the CLI comes from the result too. An integer from 0 to 255 in the `exit-code` field becomes the exit code as it is, so a command with three outcomes can hand a script "no differences", "differences" and "a step failed" as 0, 1 and 2. Without that field, a dict result with `"ok": false` ends with exit code 1, the same convention the `deploy` and `probe` reports follow. When the field disagrees with `ok`, the field decides. A string, `true` or 300 is not a code, and the CLI goes by `ok` instead. The MCP tool returns the field with the rest of the result. Do not call `sys.exit` in a handler to get a code: the same function runs inside the MCP server, where the call would never be answered and the server would stop. Argument types are `str`, `int`, `float` and `bool` for a flag. elemctl adds `env_file` to the MCP tool itself, so a plugin command reaches other environments exactly like the core tools do. A command may not take over a name the core already occupies: such a command is left out. So is a plugin that fails to load, one written for a newer core for instance. elemctl names them on stderr and in `elemctl plugins` and keeps working with the rest.
379
392
 
393
+ `context.surface` says where the call came from: `"cli"` for a subcommand and `"mcp"` for a tool. A command that answers differently on the two, briefly in a tool and in full on a terminal say, reads it there instead of guessing from how `context.log` behaves. On an older core the attribute is missing, so read it with `getattr(context, "surface", None)`.
394
+
380
395
  A positional argument may add a CLI-only key synonym: `Argument("page", cli_alias="--page")` accepts both `elemctl wiki-get 123` and `elemctl wiki-get --page 123`. The MCP tool schema keeps the one `page` parameter it always had – `cli_alias` only changes what the CLI parser accepts, nothing about the declared arguments themselves. The two forms are mutually exclusive: the parser refuses both at once, and refuses neither when the argument is required. A plugin that declares no `cli_alias` behaves exactly as before.
381
396
 
382
397
  ```bash
@@ -478,7 +493,7 @@ The project directory must follow the `{repo}/{vendor}/{name}/Проект.yaml`
478
493
  - On some platform configurations an application created from `--project-id` alone comes out as an empty skeleton with no project data. Create it from a build instead: `elemctl apps create <name> --project-id <id> --latest-build`. The `create_app` MCP tool substitutes the latest build for you. Run `elemctl deploy` once the application exists.
479
494
  - The platform describes an application created with an `Error` status only as "Неизвестная ошибка. Обратитесь к администратору". The details live in the application's task: the files, lines and columns of the compilation errors. `apps create --wait` and `apps ensure` print them after the generic text, the way `deploy` and `verify` have long done, so you do not have to dig through the server log. Not every failure ends in `Error`, though. A failed apply is rolled back to the previous build and the application comes up `Running`, so `--wait` also verifies which build the application really runs and answers with exit code 1 when it is not the one you asked for. Use `--no-verify` for the plain wait.
480
495
  - You cannot compile the sources without creating something on the platform: compilation belongs to the server and happens when a build is applied. That is what `probe` is for, and it takes the hit on a throwaway application instead of the working one. One probe costs as much time as creating an application does, so minutes. Its place is before a deploy or in CI, not in a per-keystroke loop.
481
- - A platform project is identified by the `Vendor` + `Name` pair in the manifest, not by the `Ид` in `Проект.yaml`. A build uploaded without a project id lands in the project that already owns the pair, and a second project for the same pair is refused with a 409.
496
+ - A platform project is identified by the `Ид` in `Проект.yaml`. A build uploaded without a project id lands in the project that carries its `Ид`, whatever the `Vendor` + `Name` pair of the manifest, and renames that project after itself. The pair only has to be free: a fresh `Ид` with a pair another project already holds is refused with a 409.
482
497
  - You sign in to a freshly created application with a control-panel account. The application gets its own empty user list, password sign-in is off and no account service is attached, so the accounts that work in other applications do not work here. Connecting another application's user list does not change that, and neither does enabling the local sign-in. `apps create` and `apps ensure` say so themselves: the `sign-in` field of the answer, and the same text on stderr.
483
498
  - Deleted applications stay in the platform's list with a `Deleted` status and their former `id`. On that id `apps get` and `deploy` return 404. `apps find` and `apps ensure` skip them; `apps find --include-deleted` brings the previous search behaviour back.
484
499
  - The platform refuses to delete an application that has unpublished changes in the development environment and answers HTTP 400 `FAILED_PRECONDITION`. The Console API has no forced deletion, so the control panel is the only way out. elemctl points this out in the error message.