exosphere-cli 3.0.0__tar.gz → 3.0.2__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (63) hide show
  1. {exosphere_cli-3.0.0 → exosphere_cli-3.0.2}/PKG-INFO +16 -11
  2. {exosphere_cli-3.0.0 → exosphere_cli-3.0.2}/README.md +12 -8
  3. exosphere_cli-3.0.2/pyproject.toml +132 -0
  4. exosphere_cli-3.0.0/pyproject.toml → exosphere_cli-3.0.2/pyproject.toml.orig +5 -4
  5. exosphere_cli-3.0.2/src/exosphere/__init__.py +29 -0
  6. {exosphere_cli-3.0.0 → exosphere_cli-3.0.2}/src/exosphere/commands/config.py +4 -4
  7. {exosphere_cli-3.0.0 → exosphere_cli-3.0.2}/src/exosphere/commands/host.py +11 -10
  8. {exosphere_cli-3.0.0 → exosphere_cli-3.0.2}/src/exosphere/commands/inventory.py +31 -28
  9. {exosphere_cli-3.0.0 → exosphere_cli-3.0.2}/src/exosphere/commands/report.py +7 -7
  10. {exosphere_cli-3.0.0 → exosphere_cli-3.0.2}/src/exosphere/commands/utils.py +4 -3
  11. {exosphere_cli-3.0.0 → exosphere_cli-3.0.2}/src/exosphere/commands/version.py +6 -5
  12. {exosphere_cli-3.0.0 → exosphere_cli-3.0.2}/src/exosphere/config.py +33 -10
  13. {exosphere_cli-3.0.0 → exosphere_cli-3.0.2}/src/exosphere/data.py +4 -4
  14. {exosphere_cli-3.0.0 → exosphere_cli-3.0.2}/src/exosphere/editing.py +4 -1
  15. {exosphere_cli-3.0.0 → exosphere_cli-3.0.2}/src/exosphere/errors.py +3 -1
  16. {exosphere_cli-3.0.0 → exosphere_cli-3.0.2}/src/exosphere/fspaths.py +1 -1
  17. {exosphere_cli-3.0.0 → exosphere_cli-3.0.2}/src/exosphere/inventory.py +12 -6
  18. {exosphere_cli-3.0.0 → exosphere_cli-3.0.2}/src/exosphere/main.py +14 -7
  19. {exosphere_cli-3.0.0 → exosphere_cli-3.0.2}/src/exosphere/migrations.py +12 -9
  20. {exosphere_cli-3.0.0 → exosphere_cli-3.0.2}/src/exosphere/objects.py +97 -21
  21. {exosphere_cli-3.0.0 → exosphere_cli-3.0.2}/src/exosphere/pipelining.py +3 -3
  22. {exosphere_cli-3.0.0 → exosphere_cli-3.0.2}/src/exosphere/providers/api.py +2 -2
  23. {exosphere_cli-3.0.0 → exosphere_cli-3.0.2}/src/exosphere/providers/debian.py +2 -1
  24. {exosphere_cli-3.0.0 → exosphere_cli-3.0.2}/src/exosphere/providers/factory.py +5 -3
  25. {exosphere_cli-3.0.0 → exosphere_cli-3.0.2}/src/exosphere/providers/freebsd.py +9 -9
  26. {exosphere_cli-3.0.0 → exosphere_cli-3.0.2}/src/exosphere/providers/redhat.py +11 -12
  27. {exosphere_cli-3.0.0 → exosphere_cli-3.0.2}/src/exosphere/repl.py +14 -3
  28. {exosphere_cli-3.0.0 → exosphere_cli-3.0.2}/src/exosphere/reporting.py +2 -2
  29. {exosphere_cli-3.0.0 → exosphere_cli-3.0.2}/src/exosphere/ui/app.py +18 -11
  30. {exosphere_cli-3.0.0 → exosphere_cli-3.0.2}/src/exosphere/ui/dashboard.py +15 -10
  31. {exosphere_cli-3.0.0 → exosphere_cli-3.0.2}/src/exosphere/ui/elements.py +5 -3
  32. {exosphere_cli-3.0.0 → exosphere_cli-3.0.2}/src/exosphere/ui/inventory.py +15 -10
  33. {exosphere_cli-3.0.0 → exosphere_cli-3.0.2}/src/exosphere/ui/logs.py +11 -9
  34. exosphere_cli-3.0.0/src/exosphere/__init__.py +0 -14
  35. {exosphere_cli-3.0.0 → exosphere_cli-3.0.2}/COPYRIGHT +0 -0
  36. {exosphere_cli-3.0.0 → exosphere_cli-3.0.2}/LICENSE +0 -0
  37. {exosphere_cli-3.0.0 → exosphere_cli-3.0.2}/LICENSES/Apache-2.0.txt +0 -0
  38. {exosphere_cli-3.0.0 → exosphere_cli-3.0.2}/LICENSES/CC-BY-4.0.txt +0 -0
  39. {exosphere_cli-3.0.0 → exosphere_cli-3.0.2}/LICENSES/MIT.txt +0 -0
  40. {exosphere_cli-3.0.0 → exosphere_cli-3.0.2}/LICENSES/OFL-1.1.txt +0 -0
  41. {exosphere_cli-3.0.0 → exosphere_cli-3.0.2}/src/exosphere/cli.py +0 -0
  42. {exosphere_cli-3.0.0 → exosphere_cli-3.0.2}/src/exosphere/commands/__init__.py +0 -0
  43. {exosphere_cli-3.0.0 → exosphere_cli-3.0.2}/src/exosphere/commands/connections.py +0 -0
  44. {exosphere_cli-3.0.0 → exosphere_cli-3.0.2}/src/exosphere/commands/sudo.py +0 -0
  45. {exosphere_cli-3.0.0 → exosphere_cli-3.0.2}/src/exosphere/commands/ui.py +0 -0
  46. {exosphere_cli-3.0.0 → exosphere_cli-3.0.2}/src/exosphere/context.py +0 -0
  47. {exosphere_cli-3.0.0 → exosphere_cli-3.0.2}/src/exosphere/database.py +0 -0
  48. {exosphere_cli-3.0.0 → exosphere_cli-3.0.2}/src/exosphere/providers/__init__.py +0 -0
  49. {exosphere_cli-3.0.0 → exosphere_cli-3.0.2}/src/exosphere/providers/openbsd.py +0 -0
  50. {exosphere_cli-3.0.0 → exosphere_cli-3.0.2}/src/exosphere/runners.py +0 -0
  51. {exosphere_cli-3.0.0 → exosphere_cli-3.0.2}/src/exosphere/schema/__init__.py +0 -0
  52. {exosphere_cli-3.0.0 → exosphere_cli-3.0.2}/src/exosphere/schema/host-report.schema.json +0 -0
  53. {exosphere_cli-3.0.0 → exosphere_cli-3.0.2}/src/exosphere/security.py +0 -0
  54. {exosphere_cli-3.0.0 → exosphere_cli-3.0.2}/src/exosphere/setup/__init__.py +0 -0
  55. {exosphere_cli-3.0.0 → exosphere_cli-3.0.2}/src/exosphere/setup/detect.py +0 -0
  56. {exosphere_cli-3.0.0 → exosphere_cli-3.0.2}/src/exosphere/templates/report.html.j2 +0 -0
  57. {exosphere_cli-3.0.0 → exosphere_cli-3.0.2}/src/exosphere/templates/report.md.j2 +0 -0
  58. {exosphere_cli-3.0.0 → exosphere_cli-3.0.2}/src/exosphere/templates/report.txt.j2 +0 -0
  59. {exosphere_cli-3.0.0 → exosphere_cli-3.0.2}/src/exosphere/ui/__init__.py +0 -0
  60. {exosphere_cli-3.0.0 → exosphere_cli-3.0.2}/src/exosphere/ui/context.py +0 -0
  61. {exosphere_cli-3.0.0 → exosphere_cli-3.0.2}/src/exosphere/ui/messages.py +0 -0
  62. {exosphere_cli-3.0.0 → exosphere_cli-3.0.2}/src/exosphere/ui/palette.py +0 -0
  63. {exosphere_cli-3.0.0 → exosphere_cli-3.0.2}/src/exosphere/ui/style.tcss +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: exosphere-cli
3
- Version: 3.0.0
3
+ Version: 3.0.2
4
4
  Summary: CLI/TUI driven patch reporting for remote Unix-like systems.
5
5
  Author: Alexandre Gauthier
6
6
  Author-email: Alexandre Gauthier <alex@underwares.org>
@@ -30,11 +30,12 @@ Requires-Dist: prompt-toolkit>=3.0.51
30
30
  Requires-Dist: jinja2>=3.1.6
31
31
  Requires-Dist: packaging>=24.0
32
32
  Requires-Dist: rich>=14.1.0
33
- Requires-Dist: cyclopts>=4.16.1
33
+ Requires-Dist: cyclopts>=4.16.1,<5.0.0
34
34
  Requires-Dist: filelock>=3.29.4
35
35
  Requires-Dist: pydantic>=2.12
36
36
  Requires-Python: >=3.13
37
- Project-URL: homepage, https://exosphere.readthedocs.io
37
+ Project-URL: homepage, https://exosphere.tools
38
+ Project-URL: documentation, https://docs.exosphere.tools
38
39
  Project-URL: repository, https://github.com/mrdaemon/exosphere
39
40
  Project-URL: issues, https://github.com/mrdaemon/exosphere/issues
40
41
  Description-Content-Type: text/markdown
@@ -63,8 +64,8 @@ on remote hosts.
63
64
  If you have SSH access to the hosts and your keypairs are loaded in an SSH Agent,
64
65
  you are good to go!
65
66
 
66
- Simply follow the [Quickstart Guide](https://exosphere.readthedocs.io/en/stable/quickstart.html),
67
- or see [the documentation](https://exosphere.readthedocs.io/en/stable/) to get started.
67
+ Simply follow the [Quickstart Guide](https://docs.exosphere.tools/en/stable/quickstart.html),
68
+ or see [the documentation](https://docs.exosphere.tools/en/stable/) to get started.
68
69
 
69
70
  ## Key Features
70
71
 
@@ -103,7 +104,7 @@ This includes network equipment with proprietary operating systems, etc.
103
104
  ## Documentation
104
105
 
105
106
  For installation instructions, configuration and usage examples,
106
- [full documentation](https://exosphere.readthedocs.io/) is available.
107
+ [full documentation](https://docs.exosphere.tools/) is available.
107
108
 
108
109
  ## Development
109
110
 
@@ -149,7 +150,6 @@ uv run textual run --dev -c exosphere ui
149
150
  Congratulations! Editing any of the `.tcss` files in the `ui/` directory will
150
151
  reflect changes immediately.
151
152
 
152
-
153
153
  ### Documentation Editing Quick Start
154
154
 
155
155
  To edit the documentation, you can use the following commands:
@@ -188,8 +188,10 @@ tasks are defined in the `pyproject.toml` file under the `[tool.poe.tasks]` tabl
188
188
 
189
189
  | path | description |
190
190
  | ---- | ----------- |
191
+ | `LICENSES/` | Full text of every license used by the project and its assets |
191
192
  | `docs/` | Sphinx documentation source tree |
192
193
  | `docs/source/_ext/` | Custom Sphinx extensions for the project |
194
+ | `docs/source/changelog/` | Per-release changelog entries (markdown) and their assets |
193
195
  | `examples/` | Example configuration files and reports |
194
196
  | `scripts/` | Utilitarian scripts for dev and maintenance |
195
197
  | `src/` | Main source code for the application |
@@ -224,13 +226,16 @@ Paths below are relative to `src/exosphere/` unless otherwise noted.
224
226
  | `context.py` | Context management for shared state across commands and UI |
225
227
  | `data.py` | Data models and structures for serialization and exchange |
226
228
  | `database.py` | Cache system for serialization |
229
+ | `editing.py` | External editor helper module, shared between CLI and UI |
227
230
  | `errors.py` | Exception classes and general error messages |
231
+ | `fspaths.py` | Platform-appropriate filesystem paths module, for state |
228
232
  | `inventory.py` | Inventory management subsystem |
229
233
  | `migrations.py` | Cache format migration processes |
230
234
  | `objects.py` | Main objects for representing Hosts, and most of the relevant logic |
231
235
  | `pipelining.py` | SSH pipelining implementation, including reaper thread |
232
236
  | `repl.py` | REPL module for interactive CLI usage |
233
237
  | `reporting.py` | Reporting subsystem, including templates and formatters |
238
+ | `runners.py` | SSH runners module, handles setting up remote POSIX environment |
234
239
  | `security.py` | Sudo management subsystem, including policy and utilities |
235
240
 
236
241
  Generally, most of the things Exosphere does to hosts (including connection management
@@ -245,9 +250,9 @@ Paths below are relative to `src/exosphere/` unless otherwise noted.
245
250
  | `ui/app.py` | Main Textual application class and entry point for the UI |
246
251
  | `ui/context.py` | UI Context management for shared state across UI components |
247
252
  | `ui/elements.py` | Shared UI elements, including task runners |
248
- | `ui/dashboard.py` | Dashboard view implementation |
249
- | `ui/inventory.py` | Inventory view implementation |
250
- | `ui/logs.py` | Logs view implementation |
253
+ | `ui/dashboard.py` | Dashboard screen implementation |
254
+ | `ui/inventory.py` | Inventory screen implementation |
255
+ | `ui/logs.py` | Logs screen implementation |
251
256
  | `ui/messages.py` | Screen refresh and message passing system |
252
257
  | `ui/palette.py` | Command palette providers and implementations |
253
258
 
@@ -257,7 +262,7 @@ The TCSS for all of it is in a single file under `ui/style.tcss`.
257
262
 
258
263
  This use case is not currently well supported, but it is possible to use Exosphere as a library.
259
264
  The documentation for this (alongside actual examples) is still a WIP, but you can
260
- refer to the [Online API Documentation](https://exosphere.readthedocs.io/en/stable/api/index.html)
265
+ refer to the [Online API Documentation](https://docs.exosphere.tools/en/stable/api/index.html)
261
266
  for the core functionality and objects that are considered public.
262
267
 
263
268
  ## LLM/"AI" Contributions Policy
@@ -22,8 +22,8 @@ on remote hosts.
22
22
  If you have SSH access to the hosts and your keypairs are loaded in an SSH Agent,
23
23
  you are good to go!
24
24
 
25
- Simply follow the [Quickstart Guide](https://exosphere.readthedocs.io/en/stable/quickstart.html),
26
- or see [the documentation](https://exosphere.readthedocs.io/en/stable/) to get started.
25
+ Simply follow the [Quickstart Guide](https://docs.exosphere.tools/en/stable/quickstart.html),
26
+ or see [the documentation](https://docs.exosphere.tools/en/stable/) to get started.
27
27
 
28
28
  ## Key Features
29
29
 
@@ -62,7 +62,7 @@ This includes network equipment with proprietary operating systems, etc.
62
62
  ## Documentation
63
63
 
64
64
  For installation instructions, configuration and usage examples,
65
- [full documentation](https://exosphere.readthedocs.io/) is available.
65
+ [full documentation](https://docs.exosphere.tools/) is available.
66
66
 
67
67
  ## Development
68
68
 
@@ -108,7 +108,6 @@ uv run textual run --dev -c exosphere ui
108
108
  Congratulations! Editing any of the `.tcss` files in the `ui/` directory will
109
109
  reflect changes immediately.
110
110
 
111
-
112
111
  ### Documentation Editing Quick Start
113
112
 
114
113
  To edit the documentation, you can use the following commands:
@@ -147,8 +146,10 @@ tasks are defined in the `pyproject.toml` file under the `[tool.poe.tasks]` tabl
147
146
 
148
147
  | path | description |
149
148
  | ---- | ----------- |
149
+ | `LICENSES/` | Full text of every license used by the project and its assets |
150
150
  | `docs/` | Sphinx documentation source tree |
151
151
  | `docs/source/_ext/` | Custom Sphinx extensions for the project |
152
+ | `docs/source/changelog/` | Per-release changelog entries (markdown) and their assets |
152
153
  | `examples/` | Example configuration files and reports |
153
154
  | `scripts/` | Utilitarian scripts for dev and maintenance |
154
155
  | `src/` | Main source code for the application |
@@ -183,13 +184,16 @@ Paths below are relative to `src/exosphere/` unless otherwise noted.
183
184
  | `context.py` | Context management for shared state across commands and UI |
184
185
  | `data.py` | Data models and structures for serialization and exchange |
185
186
  | `database.py` | Cache system for serialization |
187
+ | `editing.py` | External editor helper module, shared between CLI and UI |
186
188
  | `errors.py` | Exception classes and general error messages |
189
+ | `fspaths.py` | Platform-appropriate filesystem paths module, for state |
187
190
  | `inventory.py` | Inventory management subsystem |
188
191
  | `migrations.py` | Cache format migration processes |
189
192
  | `objects.py` | Main objects for representing Hosts, and most of the relevant logic |
190
193
  | `pipelining.py` | SSH pipelining implementation, including reaper thread |
191
194
  | `repl.py` | REPL module for interactive CLI usage |
192
195
  | `reporting.py` | Reporting subsystem, including templates and formatters |
196
+ | `runners.py` | SSH runners module, handles setting up remote POSIX environment |
193
197
  | `security.py` | Sudo management subsystem, including policy and utilities |
194
198
 
195
199
  Generally, most of the things Exosphere does to hosts (including connection management
@@ -204,9 +208,9 @@ Paths below are relative to `src/exosphere/` unless otherwise noted.
204
208
  | `ui/app.py` | Main Textual application class and entry point for the UI |
205
209
  | `ui/context.py` | UI Context management for shared state across UI components |
206
210
  | `ui/elements.py` | Shared UI elements, including task runners |
207
- | `ui/dashboard.py` | Dashboard view implementation |
208
- | `ui/inventory.py` | Inventory view implementation |
209
- | `ui/logs.py` | Logs view implementation |
211
+ | `ui/dashboard.py` | Dashboard screen implementation |
212
+ | `ui/inventory.py` | Inventory screen implementation |
213
+ | `ui/logs.py` | Logs screen implementation |
210
214
  | `ui/messages.py` | Screen refresh and message passing system |
211
215
  | `ui/palette.py` | Command palette providers and implementations |
212
216
 
@@ -216,7 +220,7 @@ The TCSS for all of it is in a single file under `ui/style.tcss`.
216
220
 
217
221
  This use case is not currently well supported, but it is possible to use Exosphere as a library.
218
222
  The documentation for this (alongside actual examples) is still a WIP, but you can
219
- refer to the [Online API Documentation](https://exosphere.readthedocs.io/en/stable/api/index.html)
223
+ refer to the [Online API Documentation](https://docs.exosphere.tools/en/stable/api/index.html)
220
224
  for the core functionality and objects that are considered public.
221
225
 
222
226
  ## LLM/"AI" Contributions Policy
@@ -0,0 +1,132 @@
1
+ [project]
2
+ name = "exosphere-cli"
3
+ version = "3.0.2"
4
+ description = "CLI/TUI driven patch reporting for remote Unix-like systems."
5
+ readme = "README.md"
6
+ requires-python = ">=3.13"
7
+ classifiers = [
8
+ "Development Status :: 5 - Production/Stable",
9
+ "Environment :: Console",
10
+ "Intended Audience :: System Administrators",
11
+ "Natural Language :: English",
12
+ "Operating System :: OS Independent",
13
+ "Programming Language :: Python :: 3.13",
14
+ "Programming Language :: Python :: 3.14",
15
+ "Topic :: System :: Monitoring",
16
+ "Topic :: System :: Systems Administration",
17
+ "Topic :: Utilities",
18
+ "Typing :: Typed",
19
+ ]
20
+ license = "MIT"
21
+ license-files = [
22
+ "LICENSE",
23
+ "LICENSES/*",
24
+ "COPYRIGHT",
25
+ ]
26
+ dependencies = [
27
+ "fabric>=3.2.2",
28
+ "textual>=6.7.0",
29
+ "pyyaml>=6.0.3",
30
+ "platformdirs>=4.3.8",
31
+ "prompt-toolkit>=3.0.51",
32
+ "jinja2>=3.1.6",
33
+ "packaging>=24.0",
34
+ "rich>=14.1.0",
35
+ "cyclopts>=4.16.1,<5.0.0",
36
+ "filelock>=3.29.4",
37
+ "pydantic>=2.12",
38
+ ]
39
+
40
+ [[project.authors]]
41
+ name = "Alexandre Gauthier"
42
+ email = "alex@underwares.org"
43
+
44
+ [project.urls]
45
+ homepage = "https://exosphere.tools"
46
+ documentation = "https://docs.exosphere.tools"
47
+ repository = "https://github.com/mrdaemon/exosphere"
48
+ issues = "https://github.com/mrdaemon/exosphere/issues"
49
+
50
+ [project.scripts]
51
+ exosphere = "exosphere.main:main"
52
+
53
+ [dependency-groups]
54
+ dev = [
55
+ "fabric[pytest]>=3.2.2",
56
+ "jsonschema>=4.25.1",
57
+ "poethepoet>=0.33.1",
58
+ "pyright>=1.1.400",
59
+ "pytest>=8.4.2",
60
+ "pytest-asyncio>=1.2.0",
61
+ "pytest-cov>=6.1.1",
62
+ "pytest-json-ctrf>=0.3.5",
63
+ "pytest-mock>=3.14.1",
64
+ "sphinx-rtd-theme>=3.0.2",
65
+ "ruff>=0.15.0",
66
+ "sphinx>=8.2.3",
67
+ "sphinx-autobuild>=2024.10.3",
68
+ "sphinx-lint>=1.0.0",
69
+ "sphinx-tabs>=3.4.7",
70
+ "sphinxcontrib-spelling>=8.0.2",
71
+ "textual-dev>=1.7.0",
72
+ "codespell>=2.4.0",
73
+ "myst-parser[linkify]>=4.0.0",
74
+ ]
75
+
76
+ [build-system]
77
+ requires = ["uv_build>=0.7.19,<0.13.0"]
78
+ build-backend = "uv_build"
79
+
80
+ [tool.uv.build-backend]
81
+ module-name = "exosphere"
82
+
83
+ [tool.pytest.ini_options]
84
+ testpaths = ["tests"]
85
+
86
+ [tool.codespell]
87
+ skip = "*.lock,*.woff,*.woff2,*.ttf,*.ttx,*.svg,*.png,*.gif,*.html,*.htm,docs/source/_static"
88
+
89
+ [tool.poe.tasks]
90
+ lint = "ruff check src tests scripts"
91
+ isort = "ruff check --select I --fix src tests scripts"
92
+ isort-check = "ruff check --select I src tests scripts --diff"
93
+ typecheck = "pyright src tests scripts"
94
+ ruff-format = "ruff format src tests scripts"
95
+ ruff-format-check = "ruff format --check src tests scripts --diff"
96
+ spellcheck = "codespell src tests scripts docs/source README.md"
97
+ spellcheck-fix = "codespell --write-changes src tests scripts docs/source README.md"
98
+ format = [
99
+ "ruff-format",
100
+ "isort",
101
+ ]
102
+ format-check = [
103
+ "ruff-format-check",
104
+ "isort-check",
105
+ ]
106
+ check = [
107
+ "format-check",
108
+ "lint",
109
+ "typecheck",
110
+ "spellcheck",
111
+ ]
112
+ test = "pytest -v --ctrf .tests_report.json"
113
+ coverage = "pytest --cov-report term-missing --cov-report html --cov=src tests/"
114
+ docs-build = "sphinx-build -b html -W --keep-going docs/source docs/build/html"
115
+ docs-lint = "sphinx-lint docs/source"
116
+ docs-spellcheck = "sphinx-build -b spelling -W --keep-going docs/source docs/build"
117
+ docs-linkcheck = "sphinx-build -b linkcheck docs/source docs/build/linkcheck"
118
+ docs = [
119
+ "docs-lint",
120
+ "docs-spellcheck",
121
+ "docs-build",
122
+ ]
123
+ docs-serve = "sphinx-autobuild -b html docs/source docs/build/html --port 8000 --open-browser"
124
+ docs-mkreports = "python scripts/generate_example_reports.py"
125
+ demo = "vhs scripts/demo.tape"
126
+ release-preflight = "python scripts/release_preflight.py"
127
+ release-check = [
128
+ "check",
129
+ "test",
130
+ "docs",
131
+ "release-preflight",
132
+ ]
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "exosphere-cli"
3
- version = "3.0.0"
3
+ version = "3.0.2"
4
4
  description = "CLI/TUI driven patch reporting for remote Unix-like systems."
5
5
  readme = "README.md"
6
6
  authors = [
@@ -36,7 +36,7 @@ dependencies = [
36
36
  "jinja2>=3.1.6",
37
37
  "packaging>=24.0",
38
38
  "rich>=14.1.0",
39
- "cyclopts>=4.16.1",
39
+ "cyclopts>=4.16.1,<5.0.0",
40
40
  "filelock>=3.29.4",
41
41
  "pydantic>=2.12",
42
42
  ]
@@ -65,7 +65,8 @@ dev = [
65
65
  ]
66
66
 
67
67
  [project.urls]
68
- homepage = "https://exosphere.readthedocs.io"
68
+ homepage = "https://exosphere.tools"
69
+ documentation = "https://docs.exosphere.tools"
69
70
  repository = "https://github.com/mrdaemon/exosphere"
70
71
  issues = "https://github.com/mrdaemon/exosphere/issues"
71
72
 
@@ -73,7 +74,7 @@ issues = "https://github.com/mrdaemon/exosphere/issues"
73
74
  exosphere = "exosphere.main:main"
74
75
 
75
76
  [build-system]
76
- requires = ["uv_build>=0.7.19,<0.12.0"]
77
+ requires = ["uv_build>=0.7.19,<0.13.0"]
77
78
  build-backend = "uv_build"
78
79
 
79
80
  [tool.uv.build-backend]
@@ -0,0 +1,29 @@
1
+ import importlib.metadata
2
+
3
+ from .config import Configuration
4
+
5
+ # Global Instances: configuration and GlobalState
6
+ # These are set at runtime and should be used as singletons
7
+ # to hold the global state and configuration.
8
+
9
+ app_config = Configuration() # Has default values out of the box
10
+
11
+ # Current software version, imported from pyproject metadata
12
+ __version__ = importlib.metadata.version("exosphere_cli")
13
+
14
+ # Current documentation url, from pyproject metadata
15
+ # Falls back to known good readthedocs URL to avoid disasters
16
+ _project_urls: list[str] = (
17
+ importlib.metadata.metadata("exosphere_cli").get_all("Project-URL") or []
18
+ )
19
+
20
+ docs_url = next(
21
+ (
22
+ entry.split(",", 1)[1].strip()
23
+ for entry in _project_urls
24
+ if entry.lower().startswith("documentation,")
25
+ ),
26
+ "https://exosphere.readthedocs.io",
27
+ )
28
+
29
+ __all__ = ["__version__", "app_config", "docs_url"]
@@ -40,7 +40,7 @@ def show(
40
40
  Displays the current configuration options, or the value of a specific option
41
41
  if specified.
42
42
 
43
- If `--full` is specified, it will show the entire configuration structure,
43
+ If ``--full`` is specified, it will show the entire configuration structure,
44
44
  including the inventory, beyond just the "options" section.
45
45
 
46
46
  Parameters
@@ -150,9 +150,9 @@ def diff(
150
150
  file can exclusively contain the options you want to change.
151
151
 
152
152
  This command allows you to see exactly what has been changed, optionally
153
- in its context, using the `--full` option.
153
+ in its context, using the ``--full`` option.
154
154
 
155
- For a full config dump, use the `show` command instead.
155
+ For a full config dump, use the ``show`` command instead.
156
156
 
157
157
  Parameters
158
158
  ----------
@@ -264,7 +264,7 @@ def edit(
264
264
 
265
265
  try:
266
266
  validate_config(target)
267
- except Exception as e:
267
+ except (ValueError, OSError) as e:
268
268
  err_console.print(f"[red]Configuration is invalid:[/red]\n{e}")
269
269
  if Confirm.ask("Re-open editor to fix?", default=True):
270
270
  continue
@@ -22,6 +22,7 @@ from exosphere.commands.utils import (
22
22
  err_console,
23
23
  save_inventory_state,
24
24
  )
25
+ from exosphere.errors import DataRefreshError
25
26
  from exosphere.objects import Host
26
27
 
27
28
  # Simple spinner layout for long or pre-tasks
@@ -232,10 +233,10 @@ def discover(host: HostArg, /) -> int:
232
233
  progress.add_task(f"Discovering platform for '{host.name}'", total=None)
233
234
  try:
234
235
  host.discover()
235
- except Exception as e:
236
+ except DataRefreshError as e:
236
237
  progress.console.print(
237
238
  Panel.fit(
238
- f"{str(e)}",
239
+ f"{e!s}",
239
240
  title="[red]Error[/red]",
240
241
  style="red",
241
242
  title_align="left",
@@ -265,7 +266,7 @@ def refresh(
265
266
  This command retrieves the host by name from the inventory
266
267
  and refreshes its state and available updates.
267
268
 
268
- If `--sync` is specified, the package repositories will also be
269
+ If ``--sync`` is specified, the package repositories will also be
269
270
  synchronized remotely.
270
271
 
271
272
  Parameters
@@ -277,17 +278,17 @@ def refresh(
277
278
  discover
278
279
  Also refresh platform information
279
280
  """
280
- with Progress(transient=True, *SPINNER_ARGS) as progress:
281
+ with Progress(*SPINNER_ARGS, transient=True) as progress:
281
282
  if discover:
282
283
  task = progress.add_task(
283
284
  f"Refreshing platform information for '{host.name}'", total=None
284
285
  )
285
286
  try:
286
287
  host.discover()
287
- except Exception as e:
288
+ except DataRefreshError as e:
288
289
  progress.console.print(
289
290
  Panel.fit(
290
- f"{str(e)}",
291
+ f"{e!s}",
291
292
  title="[red]Error[/red]",
292
293
  style="red",
293
294
  title_align="left",
@@ -304,10 +305,10 @@ def refresh(
304
305
  )
305
306
  try:
306
307
  host.sync_repos()
307
- except Exception as e:
308
+ except DataRefreshError as e:
308
309
  progress.console.print(
309
310
  Panel.fit(
310
- f"{str(e)}",
311
+ f"{e!s}",
311
312
  title="[red]Error[/red]",
312
313
  style="red",
313
314
  title_align="left",
@@ -321,10 +322,10 @@ def refresh(
321
322
  task = progress.add_task(f"Refreshing updates for '{host.name}'", total=None)
322
323
  try:
323
324
  host.refresh_updates()
324
- except Exception as e:
325
+ except DataRefreshError as e:
325
326
  progress.console.print(
326
327
  Panel.fit(
327
- f"{str(e)}",
328
+ f"{e!s}",
328
329
  title="[red]Error[/red]",
329
330
  style="red",
330
331
  title_align="left",
@@ -27,7 +27,7 @@ from exosphere.commands.utils import (
27
27
  save_inventory_state,
28
28
  )
29
29
  from exosphere.inventory import FilterMode, Inventory, SortField
30
- from exosphere.objects import HostOperation
30
+ from exosphere.objects import Host, HostOperation
31
31
 
32
32
  # Constants for display
33
33
  ERROR_STYLE = {
@@ -122,11 +122,11 @@ def refresh(
122
122
  Connects to hosts in the inventory and retrieves pending package
123
123
  updates.
124
124
 
125
- If `--discover` is specified, the platform information (Operating
125
+ If ``--discover`` is specified, the platform information (Operating
126
126
  System flavor, version, package manager) will also be refreshed.
127
127
  Also refreshes the online status in the process.
128
128
 
129
- If `--sync` is specified, the package repositories will also be
129
+ If ``--sync`` is specified, the package repositories will also be
130
130
  synchronized remotely.
131
131
 
132
132
  Synchronizing the package repositories involves invoking whatever mechanism
@@ -135,7 +135,7 @@ def refresh(
135
135
  with a handful of slow hosts.
136
136
 
137
137
  By default, only the progress bar is shown during the operation.
138
- If `--verbose` is specified, the name and completion status of each host
138
+ If ``--verbose`` is specified, the name and completion status of each host
139
139
  will be shown in real time.
140
140
 
141
141
  Parameters
@@ -274,7 +274,7 @@ def ping(*names: HostArg) -> int:
274
274
  error_count += 1
275
275
  if exc:
276
276
  progress.console.print(
277
- f" Host [bold]{host.name}[/bold]: [bold red]ERROR[/bold red] - {str(exc)}",
277
+ f" Host [bold]{host.name}[/bold]: [bold red]ERROR[/bold red] - {exc!s}",
278
278
  )
279
279
  else:
280
280
  progress.console.print(
@@ -342,18 +342,18 @@ def status(
342
342
  online status and whether or not the data is stale.
343
343
 
344
344
  Output can be filtered to show only hosts with pending updates
345
- (`--updates-only`) or only those with pending security updates
346
- (`--security-only`). These two filters are mutually exclusive.
345
+ (``--updates-only``) or only those with pending security updates
346
+ (``--security-only``). These two filters are mutually exclusive.
347
347
 
348
- Output can also be sorted by any column with `--sort`, optionally
349
- reversed with `--reverse`. Sorting by 'version' groups hosts by
348
+ Output can also be sorted by any column with ``--sort``, optionally
349
+ reversed with ``--reverse``. Sorting by 'version' groups hosts by
350
350
  flavor first, since versions are not comparable across flavors.
351
351
 
352
352
  When sorting by any column other than name, hosts with unknown
353
353
  or unsupported values for that column will be grouped together at the
354
354
  end.
355
355
 
356
- Use `--full` to include extra columns, such as the host description.
356
+ Use ``--full`` to include extra columns, such as the host description.
357
357
 
358
358
  No matches when filtering will exit with code 3.
359
359
 
@@ -368,7 +368,7 @@ def status(
368
368
  sort
369
369
  Sort the table by the given column
370
370
  reverse
371
- Reverse the sort order (requires `--sort`)
371
+ Reverse the sort order (requires ``--sort``)
372
372
  full
373
373
  Show additional columns, including host descriptions
374
374
  """
@@ -420,12 +420,22 @@ def status(
420
420
  caption_justify="right",
421
421
  )
422
422
 
423
+ def get_platform_value(host: Host, value: str | None) -> str:
424
+ """
425
+ Retrieve display value for host platform.
426
+ Will return appropriate placeholders as fallback.
427
+ """
428
+ if value:
429
+ return value
430
+ elif not host.supported:
431
+ return "[dim](unsupported)[/dim]"
432
+ else:
433
+ return "[dim](undiscovered)[/dim]"
434
+
423
435
  for host in hosts:
424
- # Prepare some rendering data for suffixes and placeholders
436
+ # Prepare some rendering data for suffixes
425
437
  stale_suffix = " [dim]*[/dim]" if host.is_stale else ""
426
438
  reboot_suffix = " [red]![/red]" if host.needs_reboot else ""
427
- undiscovered_status = "[dim](undiscovered)[/dim]"
428
- unsupported_status = "[dim](unsupported)[/dim]"
429
439
  empty_placeholder = "[dim]—[/dim]"
430
440
 
431
441
  # Prepare table row data
@@ -446,22 +456,12 @@ def status(
446
456
  "[bold green]Online[/bold green]" if host.online else "[red]Offline[/red]"
447
457
  ) + reboot_suffix
448
458
 
449
- # Helper function to get platform info with appropriate
450
- # handling for unsupported and undiscovered hosts
451
- def get_platform_info(value):
452
- if value:
453
- return value
454
- elif not host.supported:
455
- return unsupported_status
456
- else:
457
- return undiscovered_status
458
-
459
459
  # Construct table row for host
460
460
  row = [
461
461
  host.name,
462
- get_platform_info(host.os),
463
- get_platform_info(host.flavor),
464
- get_platform_info(host.version),
462
+ get_platform_value(host, host.os),
463
+ get_platform_value(host, host.flavor),
464
+ get_platform_value(host, host.version),
465
465
  updates,
466
466
  security_updates,
467
467
  online_status,
@@ -537,7 +537,10 @@ def clear(
537
537
 
538
538
  try:
539
539
  inventory.clear_state()
540
- except Exception as e:
540
+ except Exception as e: # noqa: BLE001
541
+ # clear_state wraps cache errors as RuntimeError, but also
542
+ # re-runs init_all() afterwards, which reopens the full config
543
+ # and host construction surfaces, so this is broad on purpose.
541
544
  err_console.print(
542
545
  Panel.fit(
543
546
  f"[bold red]Error clearing inventory state:[/bold red] {e}",
@@ -102,8 +102,8 @@ def generate(
102
102
  the report will include all hosts in the inventory.
103
103
 
104
104
  It can further be narrowed to hosts with pending updates
105
- (`--updates-only`) or pending security updates
106
- (`--security-updates-only`), which are mutually exclusive.
105
+ (``--updates-only``) or pending security updates
106
+ (``--security-updates-only``), which are mutually exclusive.
107
107
 
108
108
  Note: Undiscovered or unsupported hosts are excluded from the report.
109
109
 
@@ -120,7 +120,7 @@ def generate(
120
120
  security_only
121
121
  Only report security updates
122
122
  tee
123
- Also print report to stdout (requires `--output`)
123
+ Also print report to stdout (requires ``--output``)
124
124
  quiet
125
125
  Suppress informational messages
126
126
  navigation
@@ -200,7 +200,7 @@ def generate(
200
200
  if output:
201
201
  try:
202
202
  output.write_text(content, encoding="utf-8")
203
- except Exception as e:
203
+ except (OSError, UnicodeEncodeError) as e:
204
204
  err_console.print(f"[red]Failed to write to {output}: {e}[/red]")
205
205
  return 2 # Application error
206
206
 
@@ -241,14 +241,14 @@ def schema(
241
241
  Show or write the JSON Schema for the current version of Exosphere
242
242
 
243
243
  Emits the JSON Schema (draft-07) describing the structure produced
244
- by `report generate --format json`, for the currently running
244
+ by ``report generate --format json``, for the currently running
245
245
  version of Exosphere.
246
246
 
247
247
  This allows anyone to easily get an overview of the structure,
248
248
  validate and integrate, offline, without a local source tree or
249
249
  access to the online documentation.
250
250
 
251
- By default the schema is printed to stdout. Use `--output` to
251
+ By default the schema is printed to stdout. Use ``--output`` to
252
252
  write it to a file instead.
253
253
 
254
254
  Parameters
@@ -267,7 +267,7 @@ def schema(
267
267
  if output:
268
268
  try:
269
269
  output.write_text(content, encoding="utf-8")
270
- except Exception as e:
270
+ except (OSError, UnicodeEncodeError) as e:
271
271
  err_console.print(f"[red]Failed to write to {output}: {e}[/red]")
272
272
  return 2 # Application error
273
273