edge-containers-cli 5.3.0__tar.gz → 5.4.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 (117) hide show
  1. {edge_containers_cli-5.3.0 → edge_containers_cli-5.4.0}/PKG-INFO +3 -3
  2. {edge_containers_cli-5.3.0 → edge_containers_cli-5.4.0}/docs/reference/cli.md +32 -6
  3. {edge_containers_cli-5.3.0 → edge_containers_cli-5.4.0}/pyproject.toml +5 -2
  4. {edge_containers_cli-5.3.0 → edge_containers_cli-5.4.0}/src/edge_containers_cli/_version.py +3 -3
  5. {edge_containers_cli-5.3.0 → edge_containers_cli-5.4.0}/src/edge_containers_cli/cli.py +53 -17
  6. {edge_containers_cli-5.3.0 → edge_containers_cli-5.4.0}/src/edge_containers_cli/cmds/argo_commands.py +210 -198
  7. {edge_containers_cli-5.3.0 → edge_containers_cli-5.4.0}/src/edge_containers_cli/cmds/commands.py +36 -6
  8. {edge_containers_cli-5.3.0 → edge_containers_cli-5.4.0}/src/edge_containers_cli/cmds/demo_commands.py +32 -13
  9. {edge_containers_cli-5.3.0 → edge_containers_cli-5.4.0}/src/edge_containers_cli/cmds/k8s_commands.py +47 -20
  10. {edge_containers_cli-5.3.0 → edge_containers_cli-5.4.0}/src/edge_containers_cli/cmds/monitor.py +233 -81
  11. {edge_containers_cli-5.3.0 → edge_containers_cli-5.4.0}/src/edge_containers_cli/definitions.py +0 -9
  12. {edge_containers_cli-5.3.0 → edge_containers_cli-5.4.0}/src/edge_containers_cli/git.py +177 -2
  13. {edge_containers_cli-5.3.0 → edge_containers_cli-5.4.0}/src/edge_containers_cli.egg-info/PKG-INFO +3 -3
  14. {edge_containers_cli-5.3.0 → edge_containers_cli-5.4.0}/src/edge_containers_cli.egg-info/SOURCES.txt +4 -0
  15. {edge_containers_cli-5.3.0 → edge_containers_cli-5.4.0}/src/edge_containers_cli.egg-info/requires.txt +2 -2
  16. {edge_containers_cli-5.3.0 → edge_containers_cli-5.4.0}/src/edge_containers_cli.egg-info/scm_file_list.json +4 -0
  17. edge_containers_cli-5.4.0/src/edge_containers_cli.egg-info/scm_version.json +8 -0
  18. {edge_containers_cli-5.3.0 → edge_containers_cli-5.4.0}/tests/conftest.py +7 -0
  19. edge_containers_cli-5.4.0/tests/data/argocd.yaml +590 -0
  20. edge_containers_cli-5.4.0/tests/data/bl01t-deployment/apps/Chart.yaml +11 -0
  21. edge_containers_cli-5.4.0/tests/test_argocd.py +519 -0
  22. edge_containers_cli-5.4.0/tests/test_demo.py +57 -0
  23. edge_containers_cli-5.4.0/tests/test_git.py +283 -0
  24. edge_containers_cli-5.4.0/tests/test_k8s.py +138 -0
  25. edge_containers_cli-5.4.0/tests/test_monitor.py +553 -0
  26. edge_containers_cli-5.4.0/tests/test_push_rejection.py +220 -0
  27. {edge_containers_cli-5.3.0 → edge_containers_cli-5.4.0}/uv.lock +2 -2
  28. edge_containers_cli-5.3.0/src/edge_containers_cli.egg-info/scm_version.json +0 -8
  29. edge_containers_cli-5.3.0/tests/data/argocd.yaml +0 -306
  30. edge_containers_cli-5.3.0/tests/test_argocd.py +0 -83
  31. edge_containers_cli-5.3.0/tests/test_demo.py +0 -39
  32. edge_containers_cli-5.3.0/tests/test_k8s.py +0 -77
  33. {edge_containers_cli-5.3.0 → edge_containers_cli-5.4.0}/.copier-answers.yml +0 -0
  34. {edge_containers_cli-5.3.0 → edge_containers_cli-5.4.0}/.devcontainer/devcontainer.json +0 -0
  35. {edge_containers_cli-5.3.0 → edge_containers_cli-5.4.0}/.dockerignore +0 -0
  36. {edge_containers_cli-5.3.0 → edge_containers_cli-5.4.0}/.github/CONTRIBUTING.md +0 -0
  37. {edge_containers_cli-5.3.0 → edge_containers_cli-5.4.0}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
  38. {edge_containers_cli-5.3.0 → edge_containers_cli-5.4.0}/.github/ISSUE_TEMPLATE/issue.md +0 -0
  39. {edge_containers_cli-5.3.0 → edge_containers_cli-5.4.0}/.github/PULL_REQUEST_TEMPLATE/pull_request_template.md +0 -0
  40. {edge_containers_cli-5.3.0 → edge_containers_cli-5.4.0}/.github/pages/index.html +0 -0
  41. {edge_containers_cli-5.3.0 → edge_containers_cli-5.4.0}/.github/pages/make_switcher.py +0 -0
  42. {edge_containers_cli-5.3.0 → edge_containers_cli-5.4.0}/.github/workflows/_container.yml +0 -0
  43. {edge_containers_cli-5.3.0 → edge_containers_cli-5.4.0}/.github/workflows/_dist.yml +0 -0
  44. {edge_containers_cli-5.3.0 → edge_containers_cli-5.4.0}/.github/workflows/_docs.yml +0 -0
  45. {edge_containers_cli-5.3.0 → edge_containers_cli-5.4.0}/.github/workflows/_pypi.yml +0 -0
  46. {edge_containers_cli-5.3.0 → edge_containers_cli-5.4.0}/.github/workflows/_release.yml +0 -0
  47. {edge_containers_cli-5.3.0 → edge_containers_cli-5.4.0}/.github/workflows/_test.yml +0 -0
  48. {edge_containers_cli-5.3.0 → edge_containers_cli-5.4.0}/.github/workflows/_tox.yml +0 -0
  49. {edge_containers_cli-5.3.0 → edge_containers_cli-5.4.0}/.github/workflows/ci.yml +0 -0
  50. {edge_containers_cli-5.3.0 → edge_containers_cli-5.4.0}/.github/workflows/periodic.yml +0 -0
  51. {edge_containers_cli-5.3.0 → edge_containers_cli-5.4.0}/.gitignore +0 -0
  52. {edge_containers_cli-5.3.0 → edge_containers_cli-5.4.0}/.gitleaks.toml +0 -0
  53. {edge_containers_cli-5.3.0 → edge_containers_cli-5.4.0}/.pre-commit-config.yaml +0 -0
  54. {edge_containers_cli-5.3.0 → edge_containers_cli-5.4.0}/.python-version +0 -0
  55. {edge_containers_cli-5.3.0 → edge_containers_cli-5.4.0}/.vscode/extensions.json +0 -0
  56. {edge_containers_cli-5.3.0 → edge_containers_cli-5.4.0}/.vscode/launch.json +0 -0
  57. {edge_containers_cli-5.3.0 → edge_containers_cli-5.4.0}/.vscode/settings.json +0 -0
  58. {edge_containers_cli-5.3.0 → edge_containers_cli-5.4.0}/.vscode/tasks.json +0 -0
  59. {edge_containers_cli-5.3.0 → edge_containers_cli-5.4.0}/Dockerfile +0 -0
  60. {edge_containers_cli-5.3.0 → edge_containers_cli-5.4.0}/LICENSE +0 -0
  61. {edge_containers_cli-5.3.0 → edge_containers_cli-5.4.0}/README.md +0 -0
  62. {edge_containers_cli-5.3.0 → edge_containers_cli-5.4.0}/docs/_api.rst +0 -0
  63. {edge_containers_cli-5.3.0 → edge_containers_cli-5.4.0}/docs/_templates/custom-module-template.rst +0 -0
  64. {edge_containers_cli-5.3.0 → edge_containers_cli-5.4.0}/docs/conf.py +0 -0
  65. {edge_containers_cli-5.3.0 → edge_containers_cli-5.4.0}/docs/explanations/decisions/0001-record-architecture-decisions.md +0 -0
  66. {edge_containers_cli-5.3.0 → edge_containers_cli-5.4.0}/docs/explanations/decisions/0002-switched-to-python-copier-template.md +0 -0
  67. {edge_containers_cli-5.3.0 → edge_containers_cli-5.4.0}/docs/explanations/decisions/COPYME +0 -0
  68. {edge_containers_cli-5.3.0 → edge_containers_cli-5.4.0}/docs/explanations/decisions.md +0 -0
  69. {edge_containers_cli-5.3.0 → edge_containers_cli-5.4.0}/docs/explanations.md +0 -0
  70. {edge_containers_cli-5.3.0 → edge_containers_cli-5.4.0}/docs/genindex.md +0 -0
  71. {edge_containers_cli-5.3.0 → edge_containers_cli-5.4.0}/docs/how-to/contribute.md +0 -0
  72. {edge_containers_cli-5.3.0 → edge_containers_cli-5.4.0}/docs/how-to/run-container.md +0 -0
  73. {edge_containers_cli-5.3.0 → edge_containers_cli-5.4.0}/docs/how-to.md +0 -0
  74. {edge_containers_cli-5.3.0 → edge_containers_cli-5.4.0}/docs/images/dls-favicon.ico +0 -0
  75. {edge_containers_cli-5.3.0 → edge_containers_cli-5.4.0}/docs/images/dls-logo.svg +0 -0
  76. {edge_containers_cli-5.3.0 → edge_containers_cli-5.4.0}/docs/images/ec-logs.png +0 -0
  77. {edge_containers_cli-5.3.0 → edge_containers_cli-5.4.0}/docs/images/ec-web.png +0 -0
  78. {edge_containers_cli-5.3.0 → edge_containers_cli-5.4.0}/docs/index.md +0 -0
  79. {edge_containers_cli-5.3.0 → edge_containers_cli-5.4.0}/docs/reference/environment-variables.md +0 -0
  80. {edge_containers_cli-5.3.0 → edge_containers_cli-5.4.0}/docs/reference.md +0 -0
  81. {edge_containers_cli-5.3.0 → edge_containers_cli-5.4.0}/docs/tutorials/installation.md +0 -0
  82. {edge_containers_cli-5.3.0 → edge_containers_cli-5.4.0}/docs/tutorials.md +0 -0
  83. {edge_containers_cli-5.3.0 → edge_containers_cli-5.4.0}/renovate.json +0 -0
  84. {edge_containers_cli-5.3.0 → edge_containers_cli-5.4.0}/setup.cfg +0 -0
  85. {edge_containers_cli-5.3.0 → edge_containers_cli-5.4.0}/src/edge_containers_cli/__init__.py +0 -0
  86. {edge_containers_cli-5.3.0 → edge_containers_cli-5.4.0}/src/edge_containers_cli/__main__.py +0 -0
  87. {edge_containers_cli-5.3.0 → edge_containers_cli-5.4.0}/src/edge_containers_cli/autocomplete.py +0 -0
  88. {edge_containers_cli-5.3.0 → edge_containers_cli-5.4.0}/src/edge_containers_cli/backend.py +0 -0
  89. {edge_containers_cli-5.3.0 → edge_containers_cli-5.4.0}/src/edge_containers_cli/cmds/__init__.py +0 -0
  90. {edge_containers_cli-5.3.0 → edge_containers_cli-5.4.0}/src/edge_containers_cli/cmds/helm.py +0 -0
  91. {edge_containers_cli-5.3.0 → edge_containers_cli-5.4.0}/src/edge_containers_cli/cmds/monitor.tcss +0 -0
  92. {edge_containers_cli-5.3.0 → edge_containers_cli-5.4.0}/src/edge_containers_cli/globals.py +0 -0
  93. {edge_containers_cli-5.3.0 → edge_containers_cli-5.4.0}/src/edge_containers_cli/logging.py +0 -0
  94. {edge_containers_cli-5.3.0 → edge_containers_cli-5.4.0}/src/edge_containers_cli/shell.py +0 -0
  95. {edge_containers_cli-5.3.0 → edge_containers_cli-5.4.0}/src/edge_containers_cli/utils.py +0 -0
  96. {edge_containers_cli-5.3.0 → edge_containers_cli-5.4.0}/src/edge_containers_cli.egg-info/dependency_links.txt +0 -0
  97. {edge_containers_cli-5.3.0 → edge_containers_cli-5.4.0}/src/edge_containers_cli.egg-info/entry_points.txt +0 -0
  98. {edge_containers_cli-5.3.0 → edge_containers_cli-5.4.0}/src/edge_containers_cli.egg-info/top_level.txt +0 -0
  99. {edge_containers_cli-5.3.0 → edge_containers_cli-5.4.0}/tests/__init__.py +0 -0
  100. {edge_containers_cli-5.3.0 → edge_containers_cli-5.4.0}/tests/data/bl01t-deployment/apps/values.yaml +0 -0
  101. {edge_containers_cli-5.3.0 → edge_containers_cli-5.4.0}/tests/data/bl01t-services/services/bl01t-ea-test-01/Chart.yaml +0 -0
  102. {edge_containers_cli-5.3.0 → edge_containers_cli-5.4.0}/tests/data/bl01t-services/services/bl01t-ea-test-01/config/ioc.db +0 -0
  103. {edge_containers_cli-5.3.0 → edge_containers_cli-5.4.0}/tests/data/bl01t-services/services/bl01t-ea-test-01/config/ioc.yaml +0 -0
  104. {edge_containers_cli-5.3.0 → edge_containers_cli-5.4.0}/tests/data/bl01t-services/services/bl01t-ea-test-01/templates/configmap.yaml +0 -0
  105. {edge_containers_cli-5.3.0 → edge_containers_cli-5.4.0}/tests/data/bl01t-services/services/bl01t-ea-test-01/values.yaml +0 -0
  106. {edge_containers_cli-5.3.0 → edge_containers_cli-5.4.0}/tests/data/bl01t-services/services/bl01t-ea-test-02/Chart.yaml +0 -0
  107. {edge_containers_cli-5.3.0 → edge_containers_cli-5.4.0}/tests/data/bl01t-services/services/bl01t-ea-test-02/values.yaml +0 -0
  108. {edge_containers_cli-5.3.0 → edge_containers_cli-5.4.0}/tests/data/bl01t-services/services/dls-aravis/Chart.yaml +0 -0
  109. {edge_containers_cli-5.3.0 → edge_containers_cli-5.4.0}/tests/data/bl01t-services/services/dls-aravis/config/ioc.yaml +0 -0
  110. {edge_containers_cli-5.3.0 → edge_containers_cli-5.4.0}/tests/data/bl01t-services/services/dls-aravis/templates/configmap.yaml +0 -0
  111. {edge_containers_cli-5.3.0 → edge_containers_cli-5.4.0}/tests/data/bl01t-services/services/dls-aravis/values.yaml +0 -0
  112. {edge_containers_cli-5.3.0 → edge_containers_cli-5.4.0}/tests/data/bl01t-services/services/values.yaml +0 -0
  113. {edge_containers_cli-5.3.0 → edge_containers_cli-5.4.0}/tests/data/cli.yaml +0 -0
  114. {edge_containers_cli-5.3.0 → edge_containers_cli-5.4.0}/tests/data/k8s.yaml +0 -0
  115. {edge_containers_cli-5.3.0 → edge_containers_cli-5.4.0}/tests/data/yaml.yaml +0 -0
  116. {edge_containers_cli-5.3.0 → edge_containers_cli-5.4.0}/tests/test_cli.py +0 -0
  117. {edge_containers_cli-5.3.0 → edge_containers_cli-5.4.0}/tests/test_unit.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: edge-containers-cli
3
- Version: 5.3.0
3
+ Version: 5.4.0
4
4
  Summary: CLI for deploying and managing epics containers IOCs and services
5
5
  Author-email: Giles Knap <giles.knap@gmail.com>
6
6
  License: Apache License
@@ -216,12 +216,12 @@ Requires-Python: >=3.11
216
216
  Description-Content-Type: text/markdown
217
217
  License-File: LICENSE
218
218
  Requires-Dist: natsort
219
- Requires-Dist: typer[all]
219
+ Requires-Dist: typer
220
220
  Requires-Dist: requests
221
221
  Requires-Dist: ruamel.yaml
222
222
  Requires-Dist: jinja2
223
223
  Requires-Dist: polars-lts-cpu
224
- Requires-Dist: textual>=7.4
224
+ Requires-Dist: textual<9,>=7.4
225
225
  Requires-Dist: urllib3<2.4.0
226
226
  Dynamic: license-file
227
227
 
@@ -135,20 +135,46 @@ List all tagged versions of `SERVICE` found in the repository.
135
135
  #### `ec ps`
136
136
 
137
137
  ```
138
- $ ec ps [-r/--running-only]
138
+ $ ec ps [-r/--running-only] [-w/--wide]
139
139
  ```
140
140
 
141
- List the services in the current target as a table. `-r/--running-only`
142
- restricts the output to services that are currently running.
141
+ List the services in the current target as a table, with these columns:
142
+
143
+ | Column | Meaning |
144
+ |---|---|
145
+ | name | The service name |
146
+ | health | Argo CD's health for the service: `Healthy`, `Progressing`, `Degraded`, `Suspended`, `Missing` or `Unknown`, with ` (Stopped)` appended after `ec stop` |
147
+ | sync | Argo CD's sync status: `Synced`, `OutOfSync` or `Unknown` |
148
+ | version | The version the service is deployed at (its target revision) |
149
+ | last sync | When Argo CD last finished syncing the service. A deploy and `ec restart` both update it |
150
+ | description | The service's description (see `ec set-desc`), empty when unset |
151
+
152
+ `-w/--wide` adds a `properties` column with the service's labels.
153
+ `-r/--running-only` restricts the output to services that are `Healthy` and
154
+ not stopped.
155
+
156
+ On the Argo CD backend, `ec ps` makes a single `argocd app list` call however
157
+ many services there are. The K8S backend shows a service as `Healthy` when a
158
+ replica is ready and `Degraded` otherwise, leaves `sync` and `properties`
159
+ empty, and shows the StatefulSet's creation time as `last sync`.
143
160
 
144
161
  #### `ec monitor`
145
162
 
146
163
  ```
147
- $ ec monitor [-r/--running-only]
164
+ $ ec monitor [-r/--running-only] [-w/--wide]
148
165
  ```
149
166
 
150
- Open the interactive TUI monitor. `-r/--running-only` starts it showing only
151
- running services. The monitor can also be served as a web application — see the
167
+ Open the interactive TUI monitor. It shows the same columns as `ec ps`, with
168
+ `description` as a real column; `-w/--wide` adds `properties`, as for `ec ps`.
169
+ `-r/--running-only` starts it showing only running services.
170
+
171
+ Click a column header, or press `o`, to sort by it — the sorted column's
172
+ header is marked `▲` (ascending) or `▼` (descending). Pressing `o` again
173
+ cycles to the next column (always ascending); clicking the header that's
174
+ already the sort column, or pressing `d`, toggles its direction instead.
175
+ Sort order is kept across the monitor's background refreshes.
176
+
177
+ The monitor can also be served as a web application — see the
152
178
  [README](https://github.com/epics-containers/edge-containers-cli#monitor).
153
179
 
154
180
  #### `ec logs SERVICE`
@@ -16,12 +16,15 @@ description = "CLI for deploying and managing epics containers IOCs and services
16
16
 
17
17
  dependencies = [
18
18
  "natsort",
19
- "typer[all]",
19
+ "typer",
20
20
  "requests",
21
21
  "ruamel.yaml",
22
22
  "jinja2",
23
23
  "polars-lts-cpu",
24
- "textual>=7.4",
24
+ # <9: MonitorApp overrides the private _set_pointer_shape (see monitor.py)
25
+ # to silence Textual's unconditional OSC 22 pointer-shape escape, which
26
+ # some terminals print as garbage. Re-check that override on any bump.
27
+ "textual>=7.4,<9",
25
28
  # urllib>=2.4.0 fails to validate DLS Kubernetes certificates
26
29
  # https://github.com/kubernetes-client/python/issues/2394
27
30
  "urllib3<2.4.0",
@@ -18,7 +18,7 @@ version_tuple: tuple[int | str, ...]
18
18
  commit_id: str | None
19
19
  __commit_id__: str | None
20
20
 
21
- __version__ = version = '5.3.0'
22
- __version_tuple__ = version_tuple = (5, 3, 0)
21
+ __version__ = version = '5.4.0'
22
+ __version_tuple__ = version_tuple = (5, 4, 0)
23
23
 
24
- __commit_id__ = commit_id = 'gd1a3f17c8'
24
+ __commit_id__ = commit_id = 'ge3b4c5ecd'
@@ -1,5 +1,5 @@
1
1
  import os
2
- import re
2
+ import sys
3
3
  from pathlib import Path
4
4
 
5
5
  import rich
@@ -28,24 +28,19 @@ def confirmation(message: str, yes: bool):
28
28
  raise typer.Abort()
29
29
 
30
30
 
31
- def _check_description(desc_text: str | None):
32
- _desc_re = r"^[a-zA-Z0-9](?:(?!--)[a-zA-Z0-9-]){0,62}$"
33
- if desc_text is not None:
34
- if not re.match(_desc_re, desc_text):
35
- raise typer.BadParameter(
36
- f"The description '{desc_text}' is not kebab-case or uses illegal characters.\n\
37
- Only alphanumeric characters and '-' are allowed."
38
- )
39
- return desc_text
40
-
41
-
42
31
  class ErrorHandlingTyper(typer.Typer):
43
32
  def __call__(self, *args, **kwargs):
44
33
  try:
45
34
  super().__call__(*args, **kwargs)
46
35
  except (CommandError, ShellError, GitError) as e:
36
+ # super().__call__() has already returned/raised by this point,
37
+ # so we are outside typer/click's own invocation context and
38
+ # `raise typer.Exit(1)` here would do nothing but propagate as
39
+ # an unhandled RuntimeError - it is only meaningful when raised
40
+ # from inside a command. sys.exit() is what actually reports
41
+ # the failure to the calling shell.
47
42
  log.error(e)
48
- typer.Exit(1)
43
+ sys.exit(1)
49
44
 
50
45
 
51
46
  cli = ErrorHandlingTyper(pretty_exceptions_show_locals=False)
@@ -106,8 +101,8 @@ async def deploy(
106
101
  description: str | None = typer.Option(
107
102
  None,
108
103
  "--desc",
109
- help="Custom description label for the service",
110
- callback=_check_description,
104
+ help="Free-text description for the service (Argo CD backend only). "
105
+ "Pass an empty string to clear it.",
111
106
  ),
112
107
  wait: bool = typer.Option(False, "--wait", help="Waits for readiness"),
113
108
  yes: bool = typer.Option(False, "-y", "--yes", help="Skip confirmation prompt"),
@@ -261,13 +256,16 @@ def monitor(
261
256
  running_only: bool = typer.Option(
262
257
  False, "-r", "--running-only", help="list only services that are running"
263
258
  ),
259
+ wide: bool = typer.Option(
260
+ False, "-w", "--wide", help="also show each service's properties (labels)"
261
+ ),
264
262
  ):
265
263
  """Open monitor TUI"""
266
264
  from edge_containers_cli.cmds.monitor import (
267
265
  MonitorApp, # Lazy import for performace
268
266
  )
269
267
 
270
- app = MonitorApp(backend.commands, running_only)
268
+ app = MonitorApp(backend.commands, running_only, wide)
271
269
  app.run()
272
270
 
273
271
 
@@ -276,9 +274,12 @@ def ps(
276
274
  running_only: bool = typer.Option(
277
275
  False, "-r", "--running-only", help="list only services that are running"
278
276
  ),
277
+ wide: bool = typer.Option(
278
+ False, "-w", "--wide", help="also show each service's properties (labels)"
279
+ ),
279
280
  ):
280
281
  """List the services running in the current target"""
281
- backend.commands.ps(running_only)
282
+ backend.commands.ps(running_only, wide)
282
283
 
283
284
 
284
285
  @cli.command()
@@ -295,6 +296,41 @@ async def restart(
295
296
  await backend.commands.restart(service_name)
296
297
 
297
298
 
299
+ @cli.command(name="set-desc")
300
+ @async_command
301
+ async def set_desc(
302
+ service_name: str = typer.Argument(
303
+ ...,
304
+ help="Name of the service to update",
305
+ autocompletion=all_svc,
306
+ show_default=False,
307
+ ),
308
+ description: str = typer.Argument(
309
+ ...,
310
+ help="New free-text description for the service. Pass an empty "
311
+ "string to clear it.",
312
+ show_default=False,
313
+ ),
314
+ yes: bool = typer.Option(False, "-y", "--yes", help="Skip confirmation prompt"),
315
+ ):
316
+ """
317
+ Change a service's description without a version or --desc round trip.
318
+
319
+ Does not restart the service, and does not change its deployed
320
+ version or enabled state.
321
+ """
322
+
323
+ def confirm_callback(old_desc: str | None, new_desc: str):
324
+ message = (
325
+ f"[bold]Set description of [white]{service_name.lower()}[/white]"
326
+ f" on target [white]{backend.commands.target}[/white]"
327
+ f" from [white]{old_desc}[/white] to [white]{new_desc}[/white][/bold]"
328
+ )
329
+ confirmation(message, yes)
330
+
331
+ await backend.commands.set_description(service_name, description, confirm_callback)
332
+
333
+
298
334
  @cli.command()
299
335
  @async_command
300
336
  async def start(