bijux-cli 0.1.0__py3-none-any.whl
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.
- bijux_cli/__init__.py +39 -0
- bijux_cli/__main__.py +417 -0
- bijux_cli/__version__.py +42 -0
- bijux_cli/api.py +326 -0
- bijux_cli/cli.py +68 -0
- bijux_cli/commands/__init__.py +170 -0
- bijux_cli/commands/audit.py +293 -0
- bijux_cli/commands/config/__init__.py +85 -0
- bijux_cli/commands/config/clear.py +124 -0
- bijux_cli/commands/config/export.py +148 -0
- bijux_cli/commands/config/get.py +141 -0
- bijux_cli/commands/config/list_cmd.py +127 -0
- bijux_cli/commands/config/load.py +128 -0
- bijux_cli/commands/config/py.typed +0 -0
- bijux_cli/commands/config/reload.py +125 -0
- bijux_cli/commands/config/service.py +107 -0
- bijux_cli/commands/config/set.py +260 -0
- bijux_cli/commands/config/unset.py +140 -0
- bijux_cli/commands/dev/__init__.py +46 -0
- bijux_cli/commands/dev/di.py +285 -0
- bijux_cli/commands/dev/list_plugins.py +66 -0
- bijux_cli/commands/dev/py.typed +0 -0
- bijux_cli/commands/dev/service.py +120 -0
- bijux_cli/commands/docs.py +329 -0
- bijux_cli/commands/doctor.py +181 -0
- bijux_cli/commands/help.py +436 -0
- bijux_cli/commands/history/__init__.py +43 -0
- bijux_cli/commands/history/clear.py +160 -0
- bijux_cli/commands/history/py.typed +0 -0
- bijux_cli/commands/history/service.py +362 -0
- bijux_cli/commands/memory/__init__.py +59 -0
- bijux_cli/commands/memory/clear.py +114 -0
- bijux_cli/commands/memory/delete.py +144 -0
- bijux_cli/commands/memory/get.py +146 -0
- bijux_cli/commands/memory/list.py +115 -0
- bijux_cli/commands/memory/py.typed +0 -0
- bijux_cli/commands/memory/service.py +242 -0
- bijux_cli/commands/memory/set.py +136 -0
- bijux_cli/commands/memory/utils.py +54 -0
- bijux_cli/commands/plugins/__init__.py +59 -0
- bijux_cli/commands/plugins/check.py +240 -0
- bijux_cli/commands/plugins/info.py +143 -0
- bijux_cli/commands/plugins/install.py +327 -0
- bijux_cli/commands/plugins/list.py +73 -0
- bijux_cli/commands/plugins/py.typed +0 -0
- bijux_cli/commands/plugins/scaffold.py +287 -0
- bijux_cli/commands/plugins/uninstall.py +206 -0
- bijux_cli/commands/plugins/utils.py +158 -0
- bijux_cli/commands/py.typed +0 -0
- bijux_cli/commands/repl.py +709 -0
- bijux_cli/commands/sleep.py +169 -0
- bijux_cli/commands/status.py +289 -0
- bijux_cli/commands/utilities.py +637 -0
- bijux_cli/commands/version.py +152 -0
- bijux_cli/contracts/__init__.py +48 -0
- bijux_cli/contracts/audit.py +66 -0
- bijux_cli/contracts/config.py +127 -0
- bijux_cli/contracts/context.py +93 -0
- bijux_cli/contracts/docs.py +65 -0
- bijux_cli/contracts/doctor.py +32 -0
- bijux_cli/contracts/emitter.py +58 -0
- bijux_cli/contracts/history.py +101 -0
- bijux_cli/contracts/memory.py +76 -0
- bijux_cli/contracts/observability.py +94 -0
- bijux_cli/contracts/process.py +48 -0
- bijux_cli/contracts/py.typed +0 -0
- bijux_cli/contracts/registry.py +115 -0
- bijux_cli/contracts/retry.py +53 -0
- bijux_cli/contracts/serializer.py +100 -0
- bijux_cli/contracts/telemetry.py +46 -0
- bijux_cli/core/__init__.py +44 -0
- bijux_cli/core/constants.py +21 -0
- bijux_cli/core/context.py +218 -0
- bijux_cli/core/di.py +695 -0
- bijux_cli/core/engine.py +199 -0
- bijux_cli/core/enums.py +52 -0
- bijux_cli/core/exceptions.py +184 -0
- bijux_cli/core/paths.py +21 -0
- bijux_cli/core/py.typed +0 -0
- bijux_cli/httpapi.py +552 -0
- bijux_cli/infra/__init__.py +43 -0
- bijux_cli/infra/emitter.py +140 -0
- bijux_cli/infra/observability.py +148 -0
- bijux_cli/infra/process.py +177 -0
- bijux_cli/infra/py.typed +0 -0
- bijux_cli/infra/retry.py +300 -0
- bijux_cli/infra/serializer.py +573 -0
- bijux_cli/infra/telemetry.py +159 -0
- bijux_cli/py.typed +0 -0
- bijux_cli/services/__init__.py +233 -0
- bijux_cli/services/audit.py +268 -0
- bijux_cli/services/config.py +677 -0
- bijux_cli/services/docs.py +170 -0
- bijux_cli/services/doctor.py +34 -0
- bijux_cli/services/history.py +527 -0
- bijux_cli/services/memory.py +129 -0
- bijux_cli/services/plugins/__init__.py +346 -0
- bijux_cli/services/plugins/entrypoints.py +151 -0
- bijux_cli/services/plugins/groups.py +177 -0
- bijux_cli/services/plugins/hooks.py +113 -0
- bijux_cli/services/plugins/py.typed +0 -0
- bijux_cli/services/plugins/registry.py +284 -0
- bijux_cli/services/py.typed +0 -0
- bijux_cli/services/utils.py +68 -0
- bijux_cli-0.1.0.dist-info/METADATA +297 -0
- bijux_cli-0.1.0.dist-info/RECORD +108 -0
- bijux_cli-0.1.0.dist-info/WHEEL +4 -0
- bijux_cli-0.1.0.dist-info/entry_points.txt +2 -0
|
@@ -0,0 +1,329 @@
|
|
|
1
|
+
# SPDX-License-Identifier: MIT
|
|
2
|
+
# Copyright © 2025 Bijan Mousavi
|
|
3
|
+
|
|
4
|
+
"""Docs command for the Bijux CLI.
|
|
5
|
+
|
|
6
|
+
Generates a machine-readable specification of the entire CLI, outputting it as
|
|
7
|
+
JSON or YAML. This command is designed for automation, enabling integration
|
|
8
|
+
with external documentation tools or APIs. It supports outputting to stdout or
|
|
9
|
+
a file and ensures all text is ASCII-safe.
|
|
10
|
+
|
|
11
|
+
Output Contract:
|
|
12
|
+
* Success (file): `{"status": "written", "file": "<path>"}`
|
|
13
|
+
* Success (stdout): The raw specification string is printed directly.
|
|
14
|
+
* Spec fields: `{"version": str, "commands": list, ...}`
|
|
15
|
+
* Verbose: Adds `{"python": str, "platform": str}` to the spec.
|
|
16
|
+
* Error: `{"error": str, "code": int}`
|
|
17
|
+
|
|
18
|
+
Exit Codes:
|
|
19
|
+
* `0`: Success.
|
|
20
|
+
* `1`: Fatal or internal error.
|
|
21
|
+
* `2`: CLI argument, flag, or format error.
|
|
22
|
+
* `3`: ASCII or encoding error.
|
|
23
|
+
"""
|
|
24
|
+
|
|
25
|
+
from __future__ import annotations
|
|
26
|
+
|
|
27
|
+
from collections.abc import Mapping
|
|
28
|
+
import os
|
|
29
|
+
from pathlib import Path
|
|
30
|
+
import platform
|
|
31
|
+
|
|
32
|
+
import typer
|
|
33
|
+
import typer.core
|
|
34
|
+
|
|
35
|
+
from bijux_cli.__version__ import __version__
|
|
36
|
+
from bijux_cli.commands.utilities import (
|
|
37
|
+
contains_non_ascii_env,
|
|
38
|
+
emit_and_exit,
|
|
39
|
+
emit_error_and_exit,
|
|
40
|
+
validate_common_flags,
|
|
41
|
+
)
|
|
42
|
+
from bijux_cli.core.constants import (
|
|
43
|
+
HELP_DEBUG,
|
|
44
|
+
HELP_FORMAT,
|
|
45
|
+
HELP_NO_PRETTY,
|
|
46
|
+
HELP_QUIET,
|
|
47
|
+
HELP_VERBOSE,
|
|
48
|
+
)
|
|
49
|
+
from bijux_cli.core.enums import OutputFormat
|
|
50
|
+
|
|
51
|
+
typer.core.rich = None # type: ignore[attr-defined,assignment]
|
|
52
|
+
|
|
53
|
+
docs_app = typer.Typer( # pytype: skip-file
|
|
54
|
+
name="docs",
|
|
55
|
+
help="(-h, --help) Generate API specifications (OpenAPI-like) for Bijux CLI.",
|
|
56
|
+
rich_markup_mode=None,
|
|
57
|
+
context_settings={"help_option_names": ["-h", "--help"]},
|
|
58
|
+
no_args_is_help=False,
|
|
59
|
+
)
|
|
60
|
+
|
|
61
|
+
CLI_VERSION = __version__
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def _default_output_path(base: Path, fmt: str) -> Path:
|
|
65
|
+
"""Computes the default output file path for a CLI spec.
|
|
66
|
+
|
|
67
|
+
Args:
|
|
68
|
+
base (Path): The output directory path.
|
|
69
|
+
fmt (str): The output format extension, either "json" or "yaml".
|
|
70
|
+
|
|
71
|
+
Returns:
|
|
72
|
+
Path: The fully resolved path to the output specification file.
|
|
73
|
+
"""
|
|
74
|
+
return base / f"spec.{fmt}"
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def _resolve_output_target(out: Path | None, fmt: str) -> tuple[str, Path | None]:
|
|
78
|
+
"""Resolves the output target and file path for the CLI spec.
|
|
79
|
+
|
|
80
|
+
Determines if the output should go to stdout or a file, resolving the
|
|
81
|
+
final path if a directory is provided.
|
|
82
|
+
|
|
83
|
+
Args:
|
|
84
|
+
out (Path | None): The user-provided output path, which can be a file,
|
|
85
|
+
a directory, or '-' for stdout.
|
|
86
|
+
fmt (str): The output format extension ("json" or "yaml").
|
|
87
|
+
|
|
88
|
+
Returns:
|
|
89
|
+
tuple[str, Path | None]: A tuple containing the target and path. The
|
|
90
|
+
target is a string ("-" for stdout or a file path), and the path
|
|
91
|
+
is the resolved `Path` object or `None` for stdout.
|
|
92
|
+
"""
|
|
93
|
+
if out is None:
|
|
94
|
+
path = _default_output_path(Path.cwd(), fmt)
|
|
95
|
+
return str(path), path
|
|
96
|
+
if str(out) == "-":
|
|
97
|
+
return "-", None
|
|
98
|
+
if out.is_dir():
|
|
99
|
+
path = _default_output_path(out, fmt)
|
|
100
|
+
return str(path), path
|
|
101
|
+
return str(out), out
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
def _build_spec_payload(include_runtime: bool) -> Mapping[str, object]:
|
|
105
|
+
"""Builds the CLI specification payload.
|
|
106
|
+
|
|
107
|
+
Args:
|
|
108
|
+
include_runtime (bool): If True, includes Python and platform metadata
|
|
109
|
+
in the specification.
|
|
110
|
+
|
|
111
|
+
Returns:
|
|
112
|
+
Mapping[str, object]: A dictionary containing the CLI version, a list
|
|
113
|
+
of registered commands, and optional runtime details.
|
|
114
|
+
|
|
115
|
+
Raises:
|
|
116
|
+
ValueError: If the version string or platform metadata contains
|
|
117
|
+
non-ASCII characters.
|
|
118
|
+
"""
|
|
119
|
+
from bijux_cli.commands import list_registered_command_names
|
|
120
|
+
from bijux_cli.commands.utilities import ascii_safe
|
|
121
|
+
|
|
122
|
+
version_str = ascii_safe(CLI_VERSION, "version")
|
|
123
|
+
payload: dict[str, object] = {
|
|
124
|
+
"version": version_str,
|
|
125
|
+
"commands": list_registered_command_names(),
|
|
126
|
+
}
|
|
127
|
+
if include_runtime:
|
|
128
|
+
payload["python"] = ascii_safe(platform.python_version(), "python_version")
|
|
129
|
+
payload["platform"] = ascii_safe(platform.platform(), "platform")
|
|
130
|
+
return payload
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
OUT_OPTION = typer.Option(
|
|
134
|
+
None,
|
|
135
|
+
"--out",
|
|
136
|
+
"-o",
|
|
137
|
+
help="Output file path or '-' for stdout. If a directory is given, a default file name is used.",
|
|
138
|
+
)
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
@docs_app.callback(invoke_without_command=True)
|
|
142
|
+
def docs(
|
|
143
|
+
ctx: typer.Context,
|
|
144
|
+
out: Path | None = OUT_OPTION,
|
|
145
|
+
quiet: bool = typer.Option(False, "-q", "--quiet", help=HELP_QUIET),
|
|
146
|
+
verbose: bool = typer.Option(False, "-v", "--verbose", help=HELP_VERBOSE),
|
|
147
|
+
fmt: str = typer.Option("json", "-f", "--format", help=HELP_FORMAT),
|
|
148
|
+
pretty: bool = typer.Option(True, "--pretty/--no-pretty", help=HELP_NO_PRETTY),
|
|
149
|
+
debug: bool = typer.Option(False, "-d", "--debug", help=HELP_DEBUG),
|
|
150
|
+
) -> None:
|
|
151
|
+
"""Defines the entrypoint and logic for the `bijux docs` command.
|
|
152
|
+
|
|
153
|
+
This function orchestrates the entire specification generation process. It
|
|
154
|
+
validates CLI flags, checks for ASCII-safe environment variables, resolves
|
|
155
|
+
the output destination, builds the specification payload, and writes the
|
|
156
|
+
result to a file or stdout. All errors are handled and emitted in a
|
|
157
|
+
structured format before exiting with a specific code.
|
|
158
|
+
|
|
159
|
+
Args:
|
|
160
|
+
ctx (typer.Context): The Typer context, used for managing command state.
|
|
161
|
+
out (Path | None): The output destination: a file path, a directory, or
|
|
162
|
+
'-' to signify stdout.
|
|
163
|
+
quiet (bool): If True, suppresses all output except for errors.
|
|
164
|
+
verbose (bool): If True, includes Python and platform metadata in the spec.
|
|
165
|
+
fmt (str): The output format, either "json" or "yaml". Defaults to "json".
|
|
166
|
+
pretty (bool): If True, pretty-prints the output for human readability.
|
|
167
|
+
debug (bool): If True, enables debug diagnostics, implying `verbose`
|
|
168
|
+
and `pretty`.
|
|
169
|
+
|
|
170
|
+
Returns:
|
|
171
|
+
None:
|
|
172
|
+
|
|
173
|
+
Raises:
|
|
174
|
+
SystemExit: Exits the application with a contract-compliant status code
|
|
175
|
+
and payload upon any error, including argument validation, ASCII
|
|
176
|
+
violations, serialization failures, or I/O issues.
|
|
177
|
+
"""
|
|
178
|
+
from bijux_cli.commands.utilities import normalize_format
|
|
179
|
+
from bijux_cli.infra.serializer import OrjsonSerializer, PyYAMLSerializer
|
|
180
|
+
from bijux_cli.infra.telemetry import NullTelemetry
|
|
181
|
+
|
|
182
|
+
command = "docs"
|
|
183
|
+
effective_include_runtime = (verbose or debug) and not quiet
|
|
184
|
+
effective_pretty = True if (debug and not quiet) else pretty
|
|
185
|
+
|
|
186
|
+
fmt_lower = normalize_format(fmt)
|
|
187
|
+
|
|
188
|
+
if ctx.args:
|
|
189
|
+
stray = ctx.args[0]
|
|
190
|
+
msg = (
|
|
191
|
+
f"No such option: {stray}"
|
|
192
|
+
if stray.startswith("-")
|
|
193
|
+
else f"Too many arguments: {' '.join(ctx.args)}"
|
|
194
|
+
)
|
|
195
|
+
emit_error_and_exit(
|
|
196
|
+
msg,
|
|
197
|
+
code=2,
|
|
198
|
+
failure="args",
|
|
199
|
+
command=command,
|
|
200
|
+
fmt=fmt_lower,
|
|
201
|
+
quiet=quiet,
|
|
202
|
+
include_runtime=effective_include_runtime,
|
|
203
|
+
debug=debug,
|
|
204
|
+
)
|
|
205
|
+
|
|
206
|
+
validate_common_flags(
|
|
207
|
+
fmt,
|
|
208
|
+
command,
|
|
209
|
+
quiet,
|
|
210
|
+
include_runtime=effective_include_runtime,
|
|
211
|
+
)
|
|
212
|
+
|
|
213
|
+
if contains_non_ascii_env():
|
|
214
|
+
emit_error_and_exit(
|
|
215
|
+
"Non-ASCII characters in environment variables",
|
|
216
|
+
code=3,
|
|
217
|
+
failure="ascii_env",
|
|
218
|
+
command=command,
|
|
219
|
+
fmt=fmt_lower,
|
|
220
|
+
quiet=quiet,
|
|
221
|
+
include_runtime=effective_include_runtime,
|
|
222
|
+
debug=debug,
|
|
223
|
+
)
|
|
224
|
+
|
|
225
|
+
out_env = os.environ.get("BIJUXCLI_DOCS_OUT")
|
|
226
|
+
if out is None and out_env:
|
|
227
|
+
out = Path(out_env)
|
|
228
|
+
|
|
229
|
+
target, path = _resolve_output_target(out, fmt_lower)
|
|
230
|
+
|
|
231
|
+
try:
|
|
232
|
+
spec = _build_spec_payload(effective_include_runtime)
|
|
233
|
+
except ValueError as exc:
|
|
234
|
+
emit_error_and_exit(
|
|
235
|
+
str(exc),
|
|
236
|
+
code=3,
|
|
237
|
+
failure="ascii",
|
|
238
|
+
command=command,
|
|
239
|
+
fmt=fmt_lower,
|
|
240
|
+
quiet=quiet,
|
|
241
|
+
include_runtime=effective_include_runtime,
|
|
242
|
+
debug=debug,
|
|
243
|
+
)
|
|
244
|
+
|
|
245
|
+
output_format = OutputFormat.YAML if fmt_lower == "yaml" else OutputFormat.JSON
|
|
246
|
+
serializer = (
|
|
247
|
+
PyYAMLSerializer(NullTelemetry())
|
|
248
|
+
if output_format is OutputFormat.YAML
|
|
249
|
+
else OrjsonSerializer(NullTelemetry())
|
|
250
|
+
)
|
|
251
|
+
try:
|
|
252
|
+
content = serializer.dumps(spec, fmt=output_format, pretty=effective_pretty)
|
|
253
|
+
except Exception as exc:
|
|
254
|
+
emit_error_and_exit(
|
|
255
|
+
f"Serialization failed: {exc}",
|
|
256
|
+
code=1,
|
|
257
|
+
failure="serialize",
|
|
258
|
+
command=command,
|
|
259
|
+
fmt=fmt_lower,
|
|
260
|
+
quiet=quiet,
|
|
261
|
+
include_runtime=effective_include_runtime,
|
|
262
|
+
debug=debug,
|
|
263
|
+
)
|
|
264
|
+
|
|
265
|
+
if os.environ.get("BIJUXCLI_TEST_IO_FAIL") == "1":
|
|
266
|
+
emit_error_and_exit(
|
|
267
|
+
"Simulated I/O failure for test",
|
|
268
|
+
code=1,
|
|
269
|
+
failure="io_fail",
|
|
270
|
+
command=command,
|
|
271
|
+
fmt=fmt_lower,
|
|
272
|
+
quiet=quiet,
|
|
273
|
+
include_runtime=effective_include_runtime,
|
|
274
|
+
debug=debug,
|
|
275
|
+
)
|
|
276
|
+
|
|
277
|
+
if target == "-":
|
|
278
|
+
if not quiet:
|
|
279
|
+
typer.echo(content)
|
|
280
|
+
raise typer.Exit(0)
|
|
281
|
+
|
|
282
|
+
if path is None:
|
|
283
|
+
emit_error_and_exit(
|
|
284
|
+
"Internal error: expected non-null output path",
|
|
285
|
+
code=1,
|
|
286
|
+
failure="internal",
|
|
287
|
+
command=command,
|
|
288
|
+
fmt=fmt_lower,
|
|
289
|
+
quiet=quiet,
|
|
290
|
+
include_runtime=effective_include_runtime,
|
|
291
|
+
debug=debug,
|
|
292
|
+
)
|
|
293
|
+
|
|
294
|
+
parent = path.parent
|
|
295
|
+
if not parent.exists():
|
|
296
|
+
emit_error_and_exit(
|
|
297
|
+
f"Output directory does not exist: {parent}",
|
|
298
|
+
code=2,
|
|
299
|
+
failure="output_dir",
|
|
300
|
+
command=command,
|
|
301
|
+
fmt=fmt_lower,
|
|
302
|
+
quiet=quiet,
|
|
303
|
+
include_runtime=effective_include_runtime,
|
|
304
|
+
debug=debug,
|
|
305
|
+
)
|
|
306
|
+
|
|
307
|
+
try:
|
|
308
|
+
path.write_text(content, encoding="utf-8")
|
|
309
|
+
except Exception as exc:
|
|
310
|
+
emit_error_and_exit(
|
|
311
|
+
f"Failed to write spec: {exc}",
|
|
312
|
+
code=2,
|
|
313
|
+
failure="write",
|
|
314
|
+
command=command,
|
|
315
|
+
fmt=fmt_lower,
|
|
316
|
+
quiet=quiet,
|
|
317
|
+
include_runtime=effective_include_runtime,
|
|
318
|
+
debug=debug,
|
|
319
|
+
)
|
|
320
|
+
|
|
321
|
+
emit_and_exit(
|
|
322
|
+
{"status": "written", "file": str(path)},
|
|
323
|
+
output_format,
|
|
324
|
+
effective_pretty,
|
|
325
|
+
verbose,
|
|
326
|
+
debug,
|
|
327
|
+
quiet,
|
|
328
|
+
command,
|
|
329
|
+
)
|
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
# SPDX-License-Identifier: MIT
|
|
2
|
+
# Copyright © 2025 Bijan Mousavi
|
|
3
|
+
|
|
4
|
+
"""Implements the `doctor` command for the Bijux CLI.
|
|
5
|
+
|
|
6
|
+
This module provides the functionality for the `bijux doctor` command, which runs
|
|
7
|
+
a series of health diagnostics on the CLI's operating environment. It checks for
|
|
8
|
+
common configuration issues and reports a summary of its findings in a
|
|
9
|
+
structured, machine-readable format suitable for automation.
|
|
10
|
+
|
|
11
|
+
Output Contract:
|
|
12
|
+
* Success: `{"status": str, "summary": list[str]}`
|
|
13
|
+
* Verbose: Adds `{"python": str, "platform": str}` to the payload.
|
|
14
|
+
* Error: `{"error": str, "code": int}`
|
|
15
|
+
|
|
16
|
+
Exit Codes:
|
|
17
|
+
* `0`: Success (command ran without errors, regardless of health status).
|
|
18
|
+
* `1`: Internal or fatal error (e.g., dependency injection failure).
|
|
19
|
+
* `2`: CLI argument or flag error.
|
|
20
|
+
"""
|
|
21
|
+
|
|
22
|
+
from __future__ import annotations
|
|
23
|
+
|
|
24
|
+
from collections.abc import Mapping
|
|
25
|
+
import os
|
|
26
|
+
import platform
|
|
27
|
+
|
|
28
|
+
import typer
|
|
29
|
+
|
|
30
|
+
from bijux_cli.commands.utilities import (
|
|
31
|
+
ascii_safe,
|
|
32
|
+
emit_error_and_exit,
|
|
33
|
+
new_run_command,
|
|
34
|
+
validate_common_flags,
|
|
35
|
+
)
|
|
36
|
+
from bijux_cli.contracts import EmitterProtocol, TelemetryProtocol
|
|
37
|
+
from bijux_cli.core.constants import (
|
|
38
|
+
HELP_DEBUG,
|
|
39
|
+
HELP_FORMAT,
|
|
40
|
+
HELP_NO_PRETTY,
|
|
41
|
+
HELP_QUIET,
|
|
42
|
+
HELP_VERBOSE,
|
|
43
|
+
)
|
|
44
|
+
from bijux_cli.core.di import DIContainer
|
|
45
|
+
|
|
46
|
+
typer.core.rich = None # type: ignore[attr-defined,assignment]
|
|
47
|
+
|
|
48
|
+
doctor_app = typer.Typer( # pytype: skip-file
|
|
49
|
+
name="doctor",
|
|
50
|
+
help="Run CLI health diagnostics and environment checks.",
|
|
51
|
+
rich_markup_mode=None,
|
|
52
|
+
context_settings={"help_option_names": ["-h", "--help"]},
|
|
53
|
+
no_args_is_help=False,
|
|
54
|
+
)
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def _build_payload(include_runtime: bool) -> Mapping[str, object]:
|
|
58
|
+
"""Builds the payload summarizing CLI environment health.
|
|
59
|
+
|
|
60
|
+
This function performs a series of checks on the environment and aggregates
|
|
61
|
+
the findings into a structured payload.
|
|
62
|
+
|
|
63
|
+
Args:
|
|
64
|
+
include_runtime (bool): If True, appends Python and platform version
|
|
65
|
+
information to the payload.
|
|
66
|
+
|
|
67
|
+
Returns:
|
|
68
|
+
Mapping[str, object]: A dictionary containing the health status, a
|
|
69
|
+
summary of findings, and optional runtime details.
|
|
70
|
+
"""
|
|
71
|
+
healthy = True
|
|
72
|
+
summary: list[str] = []
|
|
73
|
+
|
|
74
|
+
if not os.environ.get("PATH", ""):
|
|
75
|
+
healthy = False
|
|
76
|
+
summary.append("Environment PATH is empty")
|
|
77
|
+
|
|
78
|
+
if os.environ.get("BIJUXCLI_TEST_FORCE_UNHEALTHY") == "1":
|
|
79
|
+
healthy = False
|
|
80
|
+
summary.append("Forced unhealthy by test environment")
|
|
81
|
+
|
|
82
|
+
if not summary:
|
|
83
|
+
summary.append(
|
|
84
|
+
"All core checks passed" if healthy else "Unknown issue detected"
|
|
85
|
+
)
|
|
86
|
+
|
|
87
|
+
payload: dict[str, object] = {
|
|
88
|
+
"status": "healthy" if healthy else "unhealthy",
|
|
89
|
+
"summary": summary,
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
if include_runtime:
|
|
93
|
+
payload["python"] = ascii_safe(platform.python_version(), "python_version")
|
|
94
|
+
payload["platform"] = ascii_safe(platform.platform(), "platform")
|
|
95
|
+
|
|
96
|
+
return payload
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
@doctor_app.callback(invoke_without_command=True)
|
|
100
|
+
def doctor(
|
|
101
|
+
ctx: typer.Context,
|
|
102
|
+
quiet: bool = typer.Option(False, "-q", "--quiet", help=HELP_QUIET),
|
|
103
|
+
verbose: bool = typer.Option(False, "-v", "--verbose", help=HELP_VERBOSE),
|
|
104
|
+
fmt: str = typer.Option("json", "-f", "--format", help=HELP_FORMAT),
|
|
105
|
+
pretty: bool = typer.Option(True, "--pretty/--no-pretty", help=HELP_NO_PRETTY),
|
|
106
|
+
debug: bool = typer.Option(False, "-d", "--debug", help=HELP_DEBUG),
|
|
107
|
+
) -> None:
|
|
108
|
+
"""Defines the entrypoint and logic for the `bijux doctor` command.
|
|
109
|
+
|
|
110
|
+
This function orchestrates the health check process. It validates all CLI
|
|
111
|
+
flags, performs critical pre-flight checks (like dependency availability),
|
|
112
|
+
and then invokes the main run utility to build and emit the health payload.
|
|
113
|
+
|
|
114
|
+
Args:
|
|
115
|
+
ctx (typer.Context): The Typer context for managing command state.
|
|
116
|
+
quiet (bool): If True, suppresses all output; the exit code is the
|
|
117
|
+
primary indicator of the outcome.
|
|
118
|
+
verbose (bool): If True, includes Python and platform details in the
|
|
119
|
+
output payload.
|
|
120
|
+
fmt (str): The output format, either "json" or "yaml". Defaults to "json".
|
|
121
|
+
pretty (bool): If True, pretty-prints the output for human readability.
|
|
122
|
+
debug (bool): If True, enables debug diagnostics, implying `verbose`
|
|
123
|
+
and `pretty`.
|
|
124
|
+
|
|
125
|
+
Returns:
|
|
126
|
+
None:
|
|
127
|
+
|
|
128
|
+
Raises:
|
|
129
|
+
SystemExit: Exits the application with a contract-compliant status code
|
|
130
|
+
and payload upon any error, such as invalid arguments or an
|
|
131
|
+
internal system failure.
|
|
132
|
+
"""
|
|
133
|
+
if ctx.invoked_subcommand:
|
|
134
|
+
return
|
|
135
|
+
|
|
136
|
+
command = "doctor"
|
|
137
|
+
|
|
138
|
+
fmt_lower = validate_common_flags(fmt, command, quiet)
|
|
139
|
+
|
|
140
|
+
if ctx.args:
|
|
141
|
+
stray = ctx.args[0]
|
|
142
|
+
msg = (
|
|
143
|
+
f"No such option: {stray}"
|
|
144
|
+
if stray.startswith("-")
|
|
145
|
+
else f"Too many arguments: {' '.join(ctx.args)}"
|
|
146
|
+
)
|
|
147
|
+
emit_error_and_exit(
|
|
148
|
+
msg,
|
|
149
|
+
code=2,
|
|
150
|
+
failure="args",
|
|
151
|
+
command=command,
|
|
152
|
+
fmt=fmt_lower,
|
|
153
|
+
quiet=quiet,
|
|
154
|
+
include_runtime=verbose,
|
|
155
|
+
debug=debug,
|
|
156
|
+
)
|
|
157
|
+
|
|
158
|
+
try:
|
|
159
|
+
DIContainer.current().resolve(EmitterProtocol)
|
|
160
|
+
DIContainer.current().resolve(TelemetryProtocol)
|
|
161
|
+
except Exception as exc:
|
|
162
|
+
emit_error_and_exit(
|
|
163
|
+
str(exc),
|
|
164
|
+
code=1,
|
|
165
|
+
failure="internal",
|
|
166
|
+
command=command,
|
|
167
|
+
fmt=fmt_lower,
|
|
168
|
+
quiet=quiet,
|
|
169
|
+
include_runtime=verbose,
|
|
170
|
+
debug=debug,
|
|
171
|
+
)
|
|
172
|
+
|
|
173
|
+
new_run_command(
|
|
174
|
+
command_name=command,
|
|
175
|
+
payload_builder=_build_payload,
|
|
176
|
+
quiet=quiet,
|
|
177
|
+
verbose=verbose,
|
|
178
|
+
fmt=fmt_lower,
|
|
179
|
+
pretty=pretty,
|
|
180
|
+
debug=debug,
|
|
181
|
+
)
|