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
bijux_cli/__init__.py
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
# SPDX-License-Identifier: MIT
|
|
2
|
+
# Copyright © 2025 Bijan Mousavi
|
|
3
|
+
|
|
4
|
+
"""The top-level package for the Bijux CLI application.
|
|
5
|
+
|
|
6
|
+
This module serves as the main public entry point for the `bijux-cli` package.
|
|
7
|
+
It exposes the key components required for both command-line execution and
|
|
8
|
+
programmatic integration.
|
|
9
|
+
|
|
10
|
+
The primary exports are:
|
|
11
|
+
* `entry_point`: The function used by `console_scripts` to start the CLI.
|
|
12
|
+
* `BijuxAPI`: A high-level, synchronous facade for using the CLI's
|
|
13
|
+
functionality within other Python applications.
|
|
14
|
+
* `version` and `api_version`: The application and plugin API versions.
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
from __future__ import annotations
|
|
18
|
+
|
|
19
|
+
from bijux_cli.__main__ import main
|
|
20
|
+
from bijux_cli.__version__ import api_version, version
|
|
21
|
+
from bijux_cli.api import BijuxAPI
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def entry_point() -> int | None:
|
|
25
|
+
"""The primary entry point for the `console_scripts` definition.
|
|
26
|
+
|
|
27
|
+
This function calls the main CLI orchestrator and catches `SystemExit`
|
|
28
|
+
exceptions to ensure a proper integer exit code is returned to the shell.
|
|
29
|
+
|
|
30
|
+
Returns:
|
|
31
|
+
int | None: The integer exit code of the CLI process.
|
|
32
|
+
"""
|
|
33
|
+
try:
|
|
34
|
+
return main()
|
|
35
|
+
except SystemExit as exc:
|
|
36
|
+
return int(exc.code or 0)
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
__all__ = ["version", "api_version", "BijuxAPI", "entry_point"]
|
bijux_cli/__main__.py
ADDED
|
@@ -0,0 +1,417 @@
|
|
|
1
|
+
# SPDX-License-Identifier: MIT
|
|
2
|
+
# Copyright © 2025 Bijan Mousavi
|
|
3
|
+
|
|
4
|
+
"""Provides the main entry point and lifecycle orchestration for the Bijux CLI.
|
|
5
|
+
|
|
6
|
+
This module is the primary entry point when the CLI is executed. It is
|
|
7
|
+
responsible for orchestrating the entire lifecycle of a command invocation,
|
|
8
|
+
from initial setup to final exit.
|
|
9
|
+
|
|
10
|
+
Key responsibilities include:
|
|
11
|
+
* **Environment Setup:** Configures structured logging (`structlog`) and
|
|
12
|
+
disables terminal colors for tests.
|
|
13
|
+
* **Argument Pre-processing:** Cleans and validates command-line arguments
|
|
14
|
+
before they are passed to the command parser.
|
|
15
|
+
* **Service Initialization:** Initializes the dependency injection container,
|
|
16
|
+
registers all default services, and starts the core `Engine`.
|
|
17
|
+
* **Application Assembly:** Builds the main `Typer` application, including
|
|
18
|
+
all commands and dynamic plugins.
|
|
19
|
+
* **Execution and Error Handling:** Invokes the Typer application, catches
|
|
20
|
+
all top-level exceptions (including `Typer` errors, custom `CommandError`
|
|
21
|
+
exceptions, and `KeyboardInterrupt`), and translates them into
|
|
22
|
+
structured error messages and standardized exit codes.
|
|
23
|
+
* **History Recording:** Persists the command to the history service after
|
|
24
|
+
execution.
|
|
25
|
+
"""
|
|
26
|
+
|
|
27
|
+
from __future__ import annotations
|
|
28
|
+
|
|
29
|
+
import contextlib
|
|
30
|
+
from contextlib import suppress
|
|
31
|
+
import importlib.metadata as importlib_metadata
|
|
32
|
+
import io
|
|
33
|
+
import json
|
|
34
|
+
import logging
|
|
35
|
+
import os
|
|
36
|
+
import sys
|
|
37
|
+
import time
|
|
38
|
+
from typing import IO, Any, AnyStr
|
|
39
|
+
|
|
40
|
+
import click
|
|
41
|
+
from click.exceptions import NoSuchOption, UsageError
|
|
42
|
+
import structlog
|
|
43
|
+
import typer
|
|
44
|
+
|
|
45
|
+
from bijux_cli.cli import build_app
|
|
46
|
+
from bijux_cli.core.di import DIContainer
|
|
47
|
+
from bijux_cli.core.engine import Engine
|
|
48
|
+
from bijux_cli.core.enums import OutputFormat
|
|
49
|
+
from bijux_cli.core.exceptions import CommandError
|
|
50
|
+
from bijux_cli.services import register_default_services
|
|
51
|
+
from bijux_cli.services.history import History
|
|
52
|
+
|
|
53
|
+
_orig_stderr = sys.stderr
|
|
54
|
+
_orig_click_echo = click.echo
|
|
55
|
+
_orig_click_secho = click.secho
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
class _FilteredStderr(io.TextIOBase):
|
|
59
|
+
"""A proxy for `sys.stderr` that filters a specific noisy plugin warning."""
|
|
60
|
+
|
|
61
|
+
def write(self, data: str) -> int:
|
|
62
|
+
"""Writes data to stderr, suppressing a specific known warning.
|
|
63
|
+
|
|
64
|
+
Args:
|
|
65
|
+
data (str): The string to write.
|
|
66
|
+
|
|
67
|
+
Returns:
|
|
68
|
+
int: The number of characters written, or 0 if suppressed.
|
|
69
|
+
"""
|
|
70
|
+
noise = "Plugin 'test-src' does not expose a Typer app via 'cli()' or 'app'"
|
|
71
|
+
if noise in data:
|
|
72
|
+
return 0
|
|
73
|
+
|
|
74
|
+
if _orig_stderr.closed:
|
|
75
|
+
return 0
|
|
76
|
+
|
|
77
|
+
return _orig_stderr.write(data)
|
|
78
|
+
|
|
79
|
+
def flush(self) -> None:
|
|
80
|
+
"""Flushes the underlying stderr stream."""
|
|
81
|
+
if not _orig_stderr.closed:
|
|
82
|
+
_orig_stderr.flush()
|
|
83
|
+
|
|
84
|
+
def __getattr__(self, name: str) -> Any:
|
|
85
|
+
"""Delegates attribute access to the original `sys.stderr`.
|
|
86
|
+
|
|
87
|
+
Args:
|
|
88
|
+
name (str): The name of the attribute to access.
|
|
89
|
+
|
|
90
|
+
Returns:
|
|
91
|
+
Any: The attribute from the original `sys.stderr`.
|
|
92
|
+
"""
|
|
93
|
+
return getattr(_orig_stderr, name)
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
sys.stderr = _FilteredStderr()
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
def _filtered_echo(
|
|
100
|
+
message: Any = None,
|
|
101
|
+
file: IO[AnyStr] | None = None,
|
|
102
|
+
nl: bool = True,
|
|
103
|
+
err: bool = False,
|
|
104
|
+
color: bool | None = None,
|
|
105
|
+
**styles: Any,
|
|
106
|
+
) -> None:
|
|
107
|
+
"""A replacement for `click.echo` that filters a known plugin warning.
|
|
108
|
+
|
|
109
|
+
Args:
|
|
110
|
+
message (Any): The message to print.
|
|
111
|
+
file (IO[AnyStr] | None): The file to write to.
|
|
112
|
+
nl (bool): If True, appends a newline.
|
|
113
|
+
err (bool): If True, writes to stderr instead of stdout.
|
|
114
|
+
color (bool | None): If True, enables color output.
|
|
115
|
+
**styles (Any): Additional style arguments for colored output.
|
|
116
|
+
|
|
117
|
+
Returns:
|
|
118
|
+
None
|
|
119
|
+
"""
|
|
120
|
+
text = "" if message is None else str(message)
|
|
121
|
+
if (
|
|
122
|
+
text.startswith("[WARN] Plugin 'test-src'")
|
|
123
|
+
and "does not expose a Typer app" in text
|
|
124
|
+
):
|
|
125
|
+
return
|
|
126
|
+
|
|
127
|
+
if styles:
|
|
128
|
+
_orig_click_secho(message, file=file, nl=nl, err=err, color=color, **styles)
|
|
129
|
+
else:
|
|
130
|
+
_orig_click_echo(message, file=file, nl=nl, err=err, color=color)
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
click.echo = _filtered_echo
|
|
134
|
+
click.secho = _filtered_echo
|
|
135
|
+
typer.echo = _filtered_echo
|
|
136
|
+
typer.secho = _filtered_echo
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
def disable_cli_colors_for_test() -> None:
|
|
140
|
+
"""Disables color output from various libraries for test environments.
|
|
141
|
+
|
|
142
|
+
This function checks for the `BIJUXCLI_TEST_MODE` environment variable and,
|
|
143
|
+
if set, attempts to disable color output to ensure clean, predictable
|
|
144
|
+
test results.
|
|
145
|
+
"""
|
|
146
|
+
if os.environ.get("BIJUXCLI_TEST_MODE") != "1":
|
|
147
|
+
return
|
|
148
|
+
os.environ["NO_COLOR"] = "1"
|
|
149
|
+
try:
|
|
150
|
+
from rich.console import Console
|
|
151
|
+
|
|
152
|
+
Console().no_color = True
|
|
153
|
+
except ImportError:
|
|
154
|
+
pass
|
|
155
|
+
try:
|
|
156
|
+
import colorama # pyright: ignore[reportMissingModuleSource]
|
|
157
|
+
|
|
158
|
+
colorama.deinit()
|
|
159
|
+
except ImportError:
|
|
160
|
+
pass
|
|
161
|
+
try:
|
|
162
|
+
import prompt_toolkit
|
|
163
|
+
|
|
164
|
+
prompt_toolkit.shortcuts.set_title = (
|
|
165
|
+
lambda text: None # pyright: ignore[reportUnknownLambdaType]
|
|
166
|
+
)
|
|
167
|
+
except ImportError: # pragma: no cover
|
|
168
|
+
pass
|
|
169
|
+
|
|
170
|
+
|
|
171
|
+
def should_record_command_history(command_line: list[str]) -> bool:
|
|
172
|
+
"""Determines whether the given command should be recorded in the history.
|
|
173
|
+
|
|
174
|
+
History recording is disabled under the following conditions:
|
|
175
|
+
* The `BIJUXCLI_DISABLE_HISTORY` environment variable is set to "1".
|
|
176
|
+
* The command line is empty.
|
|
177
|
+
* The command is "history" or "help".
|
|
178
|
+
|
|
179
|
+
Args:
|
|
180
|
+
command_line (list[str]): The list of command-line input tokens.
|
|
181
|
+
|
|
182
|
+
Returns:
|
|
183
|
+
bool: True if the command should be recorded, otherwise False.
|
|
184
|
+
"""
|
|
185
|
+
if os.environ.get("BIJUXCLI_DISABLE_HISTORY") == "1":
|
|
186
|
+
return False
|
|
187
|
+
if not command_line:
|
|
188
|
+
return False
|
|
189
|
+
return command_line[0].lower() not in {"history", "help"}
|
|
190
|
+
|
|
191
|
+
|
|
192
|
+
def is_quiet_mode(args: list[str]) -> bool:
|
|
193
|
+
"""Checks if the CLI was invoked with a quiet flag.
|
|
194
|
+
|
|
195
|
+
Args:
|
|
196
|
+
args (list[str]): The list of command-line arguments.
|
|
197
|
+
|
|
198
|
+
Returns:
|
|
199
|
+
bool: True if `--quiet` or `-q` is present, otherwise False.
|
|
200
|
+
"""
|
|
201
|
+
return any(arg in ("--quiet", "-q") for arg in args)
|
|
202
|
+
|
|
203
|
+
|
|
204
|
+
def print_json_error(msg: str, code: int = 2, quiet: bool = False) -> None:
|
|
205
|
+
"""Prints a structured JSON error message.
|
|
206
|
+
|
|
207
|
+
The message is printed to stdout for usage errors (code 2) and stderr for
|
|
208
|
+
all other errors, unless quiet mode is enabled.
|
|
209
|
+
|
|
210
|
+
Args:
|
|
211
|
+
msg (str): The error message.
|
|
212
|
+
code (int): The error code to include in the JSON payload.
|
|
213
|
+
quiet (bool): If True, suppresses all output.
|
|
214
|
+
"""
|
|
215
|
+
if not quiet:
|
|
216
|
+
print(
|
|
217
|
+
json.dumps({"error": msg, "code": code}),
|
|
218
|
+
file=sys.stdout if code == 2 else sys.stderr,
|
|
219
|
+
)
|
|
220
|
+
|
|
221
|
+
|
|
222
|
+
def get_usage_for_args(args: list[str], app: typer.Typer) -> str:
|
|
223
|
+
"""Gets the CLI help message for a given set of arguments.
|
|
224
|
+
|
|
225
|
+
This function simulates invoking the CLI with `--help` to capture the
|
|
226
|
+
contextual help message without exiting the process.
|
|
227
|
+
|
|
228
|
+
Args:
|
|
229
|
+
args (list[str]): The CLI arguments leading up to the help flag.
|
|
230
|
+
app (typer.Typer): The `Typer` application instance.
|
|
231
|
+
|
|
232
|
+
Returns:
|
|
233
|
+
str: The generated help/usage message.
|
|
234
|
+
"""
|
|
235
|
+
from contextlib import redirect_stdout
|
|
236
|
+
import io
|
|
237
|
+
|
|
238
|
+
subcmds = []
|
|
239
|
+
for arg in args:
|
|
240
|
+
if arg in ("--help", "-h"):
|
|
241
|
+
break
|
|
242
|
+
subcmds.append(arg)
|
|
243
|
+
|
|
244
|
+
with io.StringIO() as buf, redirect_stdout(buf):
|
|
245
|
+
with suppress(SystemExit):
|
|
246
|
+
app(subcmds + ["--help"], standalone_mode=False)
|
|
247
|
+
return buf.getvalue()
|
|
248
|
+
|
|
249
|
+
|
|
250
|
+
def _strip_format_help(args: list[str]) -> list[str]:
|
|
251
|
+
"""Removes an ambiguous `--format --help` combination from arguments.
|
|
252
|
+
|
|
253
|
+
This prevents a parsing error where `--help` could be interpreted as the
|
|
254
|
+
value for the `--format` option.
|
|
255
|
+
|
|
256
|
+
Args:
|
|
257
|
+
args (list[str]): The original list of command-line arguments.
|
|
258
|
+
|
|
259
|
+
Returns:
|
|
260
|
+
list[str]: A filtered list of arguments.
|
|
261
|
+
"""
|
|
262
|
+
new_args = []
|
|
263
|
+
skip_next = False
|
|
264
|
+
for i, arg in enumerate(args):
|
|
265
|
+
if skip_next:
|
|
266
|
+
skip_next = False
|
|
267
|
+
continue
|
|
268
|
+
if (
|
|
269
|
+
arg in ("--format", "-f")
|
|
270
|
+
and i + 1 < len(args)
|
|
271
|
+
and args[i + 1] in ("--help", "-h")
|
|
272
|
+
):
|
|
273
|
+
skip_next = True
|
|
274
|
+
continue
|
|
275
|
+
new_args.append(arg)
|
|
276
|
+
return new_args
|
|
277
|
+
|
|
278
|
+
|
|
279
|
+
def check_missing_format_argument(args: list[str]) -> str | None:
|
|
280
|
+
"""Checks if a `--format` or `-f` flag is missing its required value.
|
|
281
|
+
|
|
282
|
+
Args:
|
|
283
|
+
args (list[str]): The list of command-line arguments.
|
|
284
|
+
|
|
285
|
+
Returns:
|
|
286
|
+
str | None: An error message if the value is missing, otherwise None.
|
|
287
|
+
"""
|
|
288
|
+
for i, arg in enumerate(args):
|
|
289
|
+
if arg in ("--format", "-f"):
|
|
290
|
+
if i + 1 >= len(args):
|
|
291
|
+
return "Option '--format' requires an argument"
|
|
292
|
+
next_arg = args[i + 1]
|
|
293
|
+
if next_arg.startswith("-"):
|
|
294
|
+
return "Option '--format' requires an argument"
|
|
295
|
+
return None
|
|
296
|
+
|
|
297
|
+
|
|
298
|
+
def setup_structlog(debug: bool = False) -> None:
|
|
299
|
+
"""Configures `structlog` for the application.
|
|
300
|
+
|
|
301
|
+
Args:
|
|
302
|
+
debug (bool): If True, configures human-readable console output at the
|
|
303
|
+
DEBUG level. If False, configures JSON output at the CRITICAL level.
|
|
304
|
+
"""
|
|
305
|
+
level = logging.DEBUG if debug else logging.CRITICAL
|
|
306
|
+
logging.basicConfig(level=level, stream=sys.stderr, format="%(message)s")
|
|
307
|
+
|
|
308
|
+
structlog.configure(
|
|
309
|
+
processors=[
|
|
310
|
+
structlog.contextvars.merge_contextvars,
|
|
311
|
+
structlog.stdlib.add_log_level,
|
|
312
|
+
structlog.processors.TimeStamper(fmt="iso"),
|
|
313
|
+
structlog.processors.UnicodeDecoder(),
|
|
314
|
+
(
|
|
315
|
+
structlog.dev.ConsoleRenderer()
|
|
316
|
+
if debug
|
|
317
|
+
else structlog.processors.JSONRenderer()
|
|
318
|
+
),
|
|
319
|
+
],
|
|
320
|
+
logger_factory=structlog.stdlib.LoggerFactory(),
|
|
321
|
+
wrapper_class=structlog.stdlib.BoundLogger,
|
|
322
|
+
cache_logger_on_first_use=True,
|
|
323
|
+
)
|
|
324
|
+
|
|
325
|
+
|
|
326
|
+
def main() -> int:
|
|
327
|
+
"""The main entry point for the Bijux CLI.
|
|
328
|
+
|
|
329
|
+
This function orchestrates the entire lifecycle of a CLI command, from
|
|
330
|
+
argument parsing and setup to execution and history recording.
|
|
331
|
+
|
|
332
|
+
Returns:
|
|
333
|
+
int: The final exit code of the command.
|
|
334
|
+
* `0`: Success.
|
|
335
|
+
* `1`: A generic command error occurred.
|
|
336
|
+
* `2`: A usage error or invalid option was provided.
|
|
337
|
+
* `130`: The process was interrupted by the user (Ctrl+C).
|
|
338
|
+
"""
|
|
339
|
+
args = _strip_format_help(sys.argv[1:])
|
|
340
|
+
|
|
341
|
+
quiet = is_quiet_mode(args)
|
|
342
|
+
if quiet:
|
|
343
|
+
with contextlib.suppress(Exception):
|
|
344
|
+
sys.stderr = open(os.devnull, "w") # noqa: SIM115
|
|
345
|
+
debug = "--debug" in sys.argv or os.environ.get("BIJUXCLI_DEBUG") == "1"
|
|
346
|
+
setup_structlog(debug)
|
|
347
|
+
disable_cli_colors_for_test()
|
|
348
|
+
|
|
349
|
+
if any(a in ("--version", "-V") for a in args):
|
|
350
|
+
try:
|
|
351
|
+
ver = importlib_metadata.version("bijux-cli")
|
|
352
|
+
except importlib_metadata.PackageNotFoundError:
|
|
353
|
+
ver = "unknown"
|
|
354
|
+
print(json.dumps({"version": ver}))
|
|
355
|
+
return 0
|
|
356
|
+
|
|
357
|
+
container = DIContainer.current()
|
|
358
|
+
register_default_services(
|
|
359
|
+
container, debug=False, output_format=OutputFormat.JSON, quiet=False
|
|
360
|
+
)
|
|
361
|
+
|
|
362
|
+
Engine()
|
|
363
|
+
app = build_app()
|
|
364
|
+
|
|
365
|
+
if any(a in ("-h", "--help") for a in args):
|
|
366
|
+
print(get_usage_for_args(args, app))
|
|
367
|
+
return 0
|
|
368
|
+
|
|
369
|
+
missing_format_msg = check_missing_format_argument(args)
|
|
370
|
+
if missing_format_msg:
|
|
371
|
+
print_json_error(missing_format_msg, 2, quiet)
|
|
372
|
+
return 2
|
|
373
|
+
|
|
374
|
+
command_line = args
|
|
375
|
+
start = time.time()
|
|
376
|
+
exit_code = 0
|
|
377
|
+
|
|
378
|
+
try:
|
|
379
|
+
result = app(args=command_line, standalone_mode=False)
|
|
380
|
+
exit_code = int(result) if isinstance(result, int) else 0
|
|
381
|
+
except typer.Exit as exc:
|
|
382
|
+
exit_code = exc.exit_code
|
|
383
|
+
except NoSuchOption as exc:
|
|
384
|
+
print_json_error(f"No such option: {exc.option_name}", 2, quiet)
|
|
385
|
+
exit_code = 2
|
|
386
|
+
except UsageError as exc:
|
|
387
|
+
print_json_error(str(exc), 2, quiet)
|
|
388
|
+
exit_code = 2
|
|
389
|
+
except CommandError as exc:
|
|
390
|
+
print_json_error(str(exc), 1, quiet)
|
|
391
|
+
exit_code = 1
|
|
392
|
+
except KeyboardInterrupt:
|
|
393
|
+
print_json_error("Aborted by user", 130, quiet)
|
|
394
|
+
exit_code = 130
|
|
395
|
+
except Exception as exc:
|
|
396
|
+
print_json_error(f"Unexpected error: {exc}", 1, quiet)
|
|
397
|
+
exit_code = 1
|
|
398
|
+
|
|
399
|
+
if should_record_command_history(command_line):
|
|
400
|
+
try:
|
|
401
|
+
history_service = container.resolve(History)
|
|
402
|
+
history_service.add(
|
|
403
|
+
command=" ".join(command_line),
|
|
404
|
+
params=command_line[1:],
|
|
405
|
+
success=(exit_code == 0),
|
|
406
|
+
return_code=exit_code,
|
|
407
|
+
duration_ms=int((time.time() - start) * 1000),
|
|
408
|
+
)
|
|
409
|
+
except Exception as exc:
|
|
410
|
+
print(f"[error] Could not record command history: {exc}", file=sys.stderr)
|
|
411
|
+
exit_code = 1
|
|
412
|
+
|
|
413
|
+
return exit_code
|
|
414
|
+
|
|
415
|
+
|
|
416
|
+
if __name__ == "__main__":
|
|
417
|
+
sys.exit(main()) # pragma: no cover
|
bijux_cli/__version__.py
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
# SPDX-License-Identifier: MIT
|
|
2
|
+
# Copyright © 2025 Bijan Mousavi
|
|
3
|
+
|
|
4
|
+
"""Provides version metadata for the Bijux CLI package.
|
|
5
|
+
|
|
6
|
+
This module dynamically retrieves version information from the installed
|
|
7
|
+
package's metadata and the project's pyproject.toml file. This ensures that
|
|
8
|
+
versions are managed from a single source of truth.
|
|
9
|
+
|
|
10
|
+
It exposes two primary versions:
|
|
11
|
+
* `version`: The main application version (`packaging.version.Version`).
|
|
12
|
+
* `api_version`: A separate version for the plugin API, used for
|
|
13
|
+
compatibility checks.
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
from __future__ import annotations
|
|
17
|
+
|
|
18
|
+
from importlib.metadata import PackageNotFoundError
|
|
19
|
+
from importlib.metadata import version as _get_version
|
|
20
|
+
from pathlib import Path
|
|
21
|
+
import tomllib
|
|
22
|
+
|
|
23
|
+
from packaging.version import Version
|
|
24
|
+
|
|
25
|
+
try:
|
|
26
|
+
__version__: str = _get_version("bijux-cli")
|
|
27
|
+
except PackageNotFoundError:
|
|
28
|
+
__version__ = "0.1.0"
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
try:
|
|
32
|
+
pyproject_path = Path(__file__).parent.parent.parent / "pyproject.toml"
|
|
33
|
+
with pyproject_path.open("rb") as f:
|
|
34
|
+
pyproject_data = tomllib.load(f)
|
|
35
|
+
__api_version__: str = pyproject_data["tool"]["bijux"]["api_version"]
|
|
36
|
+
except (FileNotFoundError, KeyError):
|
|
37
|
+
__api_version__ = "0.1.0"
|
|
38
|
+
|
|
39
|
+
version = Version(__version__)
|
|
40
|
+
api_version = Version(__api_version__)
|
|
41
|
+
|
|
42
|
+
__all__ = ["version", "api_version", "__version__", "__api_version__"]
|