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.
Files changed (108) hide show
  1. bijux_cli/__init__.py +39 -0
  2. bijux_cli/__main__.py +417 -0
  3. bijux_cli/__version__.py +42 -0
  4. bijux_cli/api.py +326 -0
  5. bijux_cli/cli.py +68 -0
  6. bijux_cli/commands/__init__.py +170 -0
  7. bijux_cli/commands/audit.py +293 -0
  8. bijux_cli/commands/config/__init__.py +85 -0
  9. bijux_cli/commands/config/clear.py +124 -0
  10. bijux_cli/commands/config/export.py +148 -0
  11. bijux_cli/commands/config/get.py +141 -0
  12. bijux_cli/commands/config/list_cmd.py +127 -0
  13. bijux_cli/commands/config/load.py +128 -0
  14. bijux_cli/commands/config/py.typed +0 -0
  15. bijux_cli/commands/config/reload.py +125 -0
  16. bijux_cli/commands/config/service.py +107 -0
  17. bijux_cli/commands/config/set.py +260 -0
  18. bijux_cli/commands/config/unset.py +140 -0
  19. bijux_cli/commands/dev/__init__.py +46 -0
  20. bijux_cli/commands/dev/di.py +285 -0
  21. bijux_cli/commands/dev/list_plugins.py +66 -0
  22. bijux_cli/commands/dev/py.typed +0 -0
  23. bijux_cli/commands/dev/service.py +120 -0
  24. bijux_cli/commands/docs.py +329 -0
  25. bijux_cli/commands/doctor.py +181 -0
  26. bijux_cli/commands/help.py +436 -0
  27. bijux_cli/commands/history/__init__.py +43 -0
  28. bijux_cli/commands/history/clear.py +160 -0
  29. bijux_cli/commands/history/py.typed +0 -0
  30. bijux_cli/commands/history/service.py +362 -0
  31. bijux_cli/commands/memory/__init__.py +59 -0
  32. bijux_cli/commands/memory/clear.py +114 -0
  33. bijux_cli/commands/memory/delete.py +144 -0
  34. bijux_cli/commands/memory/get.py +146 -0
  35. bijux_cli/commands/memory/list.py +115 -0
  36. bijux_cli/commands/memory/py.typed +0 -0
  37. bijux_cli/commands/memory/service.py +242 -0
  38. bijux_cli/commands/memory/set.py +136 -0
  39. bijux_cli/commands/memory/utils.py +54 -0
  40. bijux_cli/commands/plugins/__init__.py +59 -0
  41. bijux_cli/commands/plugins/check.py +240 -0
  42. bijux_cli/commands/plugins/info.py +143 -0
  43. bijux_cli/commands/plugins/install.py +327 -0
  44. bijux_cli/commands/plugins/list.py +73 -0
  45. bijux_cli/commands/plugins/py.typed +0 -0
  46. bijux_cli/commands/plugins/scaffold.py +287 -0
  47. bijux_cli/commands/plugins/uninstall.py +206 -0
  48. bijux_cli/commands/plugins/utils.py +158 -0
  49. bijux_cli/commands/py.typed +0 -0
  50. bijux_cli/commands/repl.py +709 -0
  51. bijux_cli/commands/sleep.py +169 -0
  52. bijux_cli/commands/status.py +289 -0
  53. bijux_cli/commands/utilities.py +637 -0
  54. bijux_cli/commands/version.py +152 -0
  55. bijux_cli/contracts/__init__.py +48 -0
  56. bijux_cli/contracts/audit.py +66 -0
  57. bijux_cli/contracts/config.py +127 -0
  58. bijux_cli/contracts/context.py +93 -0
  59. bijux_cli/contracts/docs.py +65 -0
  60. bijux_cli/contracts/doctor.py +32 -0
  61. bijux_cli/contracts/emitter.py +58 -0
  62. bijux_cli/contracts/history.py +101 -0
  63. bijux_cli/contracts/memory.py +76 -0
  64. bijux_cli/contracts/observability.py +94 -0
  65. bijux_cli/contracts/process.py +48 -0
  66. bijux_cli/contracts/py.typed +0 -0
  67. bijux_cli/contracts/registry.py +115 -0
  68. bijux_cli/contracts/retry.py +53 -0
  69. bijux_cli/contracts/serializer.py +100 -0
  70. bijux_cli/contracts/telemetry.py +46 -0
  71. bijux_cli/core/__init__.py +44 -0
  72. bijux_cli/core/constants.py +21 -0
  73. bijux_cli/core/context.py +218 -0
  74. bijux_cli/core/di.py +695 -0
  75. bijux_cli/core/engine.py +199 -0
  76. bijux_cli/core/enums.py +52 -0
  77. bijux_cli/core/exceptions.py +184 -0
  78. bijux_cli/core/paths.py +21 -0
  79. bijux_cli/core/py.typed +0 -0
  80. bijux_cli/httpapi.py +552 -0
  81. bijux_cli/infra/__init__.py +43 -0
  82. bijux_cli/infra/emitter.py +140 -0
  83. bijux_cli/infra/observability.py +148 -0
  84. bijux_cli/infra/process.py +177 -0
  85. bijux_cli/infra/py.typed +0 -0
  86. bijux_cli/infra/retry.py +300 -0
  87. bijux_cli/infra/serializer.py +573 -0
  88. bijux_cli/infra/telemetry.py +159 -0
  89. bijux_cli/py.typed +0 -0
  90. bijux_cli/services/__init__.py +233 -0
  91. bijux_cli/services/audit.py +268 -0
  92. bijux_cli/services/config.py +677 -0
  93. bijux_cli/services/docs.py +170 -0
  94. bijux_cli/services/doctor.py +34 -0
  95. bijux_cli/services/history.py +527 -0
  96. bijux_cli/services/memory.py +129 -0
  97. bijux_cli/services/plugins/__init__.py +346 -0
  98. bijux_cli/services/plugins/entrypoints.py +151 -0
  99. bijux_cli/services/plugins/groups.py +177 -0
  100. bijux_cli/services/plugins/hooks.py +113 -0
  101. bijux_cli/services/plugins/py.typed +0 -0
  102. bijux_cli/services/plugins/registry.py +284 -0
  103. bijux_cli/services/py.typed +0 -0
  104. bijux_cli/services/utils.py +68 -0
  105. bijux_cli-0.1.0.dist-info/METADATA +297 -0
  106. bijux_cli-0.1.0.dist-info/RECORD +108 -0
  107. bijux_cli-0.1.0.dist-info/WHEEL +4 -0
  108. bijux_cli-0.1.0.dist-info/entry_points.txt +2 -0
@@ -0,0 +1,293 @@
1
+ # SPDX-License-Identifier: MIT
2
+ # Copyright © 2025 Bijan Mousavi
3
+
4
+ """Audit command for the Bijux CLI.
5
+
6
+ Audits the current environment and configuration, emitting machine-readable structured
7
+ output in JSON or YAML. Supports dry-run simulation and writing results to a file.
8
+ Handles ASCII hygiene and structured error contracts. Output is automation-safe and
9
+ suitable for scripting or monitoring.
10
+
11
+ Output Contract:
12
+ * Success: `{"status": "completed"}`
13
+ * Dry-run: `{"status": "dry-run"}`
14
+ * Written: `{"status": "written", "file": "<path>"}`
15
+ * Verbose: `{"python": str, "platform": str}`
16
+ * Error: `{"error": str, "code": int}`
17
+
18
+ Exit Codes:
19
+ * `0`: Success, dry-run, or write success.
20
+ * `1`: Unexpected/internal error.
21
+ * `2`: CLI argument/flag/format or output-path error.
22
+ * `3`: ASCII/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
+
34
+ from bijux_cli.commands.utilities import (
35
+ ascii_safe,
36
+ contains_non_ascii_env,
37
+ emit_error_and_exit,
38
+ new_run_command,
39
+ validate_common_flags,
40
+ validate_env_file_if_present,
41
+ )
42
+ from bijux_cli.contracts import EmitterProtocol
43
+ from bijux_cli.core.constants import (
44
+ HELP_DEBUG,
45
+ HELP_FORMAT,
46
+ HELP_NO_PRETTY,
47
+ HELP_QUIET,
48
+ HELP_VERBOSE,
49
+ )
50
+ from bijux_cli.core.di import DIContainer
51
+ from bijux_cli.core.enums import OutputFormat
52
+
53
+ typer.core.rich = None # type: ignore[attr-defined,assignment]
54
+
55
+ audit_app = typer.Typer( # pytype: skip-file
56
+ name="audit",
57
+ help="Audit the current environment for configuration and state issues.",
58
+ rich_markup_mode=None,
59
+ context_settings={
60
+ "help_option_names": ["-h", "--help"],
61
+ "ignore_unknown_options": True,
62
+ "allow_extra_args": True,
63
+ },
64
+ no_args_is_help=False,
65
+ )
66
+
67
+
68
+ OUTPUT_OPTION = typer.Option(
69
+ None, "--output", "-o", help="Write output to file instead of stdout."
70
+ )
71
+ DRY_RUN_OPTION = typer.Option(
72
+ False, "--dry-run", help="Simulate audit without making changes."
73
+ )
74
+
75
+
76
+ def _build_payload(include_runtime: bool, dry_run: bool) -> Mapping[str, object]:
77
+ """Builds the structured result payload for the audit command.
78
+
79
+ Args:
80
+ include_runtime (bool): If True, runtime metadata (Python version,
81
+ platform) is included in the payload.
82
+ dry_run (bool): If True, indicates the audit is a simulation, which
83
+ sets the status field in the payload to "dry-run".
84
+
85
+ Returns:
86
+ Mapping[str, object]: A dictionary containing the structured audit results.
87
+ """
88
+ payload: dict[str, object] = {"status": "dry-run" if dry_run else "completed"}
89
+ if include_runtime:
90
+ payload["python"] = ascii_safe(platform.python_version(), "python_version")
91
+ payload["platform"] = ascii_safe(platform.platform(), "platform")
92
+ return payload
93
+
94
+
95
+ def _write_output_file(
96
+ output_path: Path,
97
+ payload: Mapping[str, object],
98
+ emitter: EmitterProtocol,
99
+ fmt: OutputFormat,
100
+ pretty: bool,
101
+ debug: bool,
102
+ dry_run: bool,
103
+ ) -> None:
104
+ """Writes the audit payload to a specified file.
105
+
106
+ This function serializes the payload to JSON or YAML and writes it to the
107
+ given file path. It will fail if the parent directory does not exist.
108
+
109
+ Args:
110
+ output_path (Path): The file path where the payload will be written.
111
+ payload (Mapping[str, object]): The data to serialize and write.
112
+ emitter (EmitterProtocol): The service responsible for serialization and
113
+ output.
114
+ fmt (OutputFormat): The desired output format (JSON or YAML).
115
+ pretty (bool): If True, the output is formatted for human readability.
116
+ debug (bool): If True, enables debug-level logging during emission.
117
+ dry_run (bool): If True, logs a message indicating a dry run.
118
+
119
+ Returns:
120
+ None:
121
+
122
+ Raises:
123
+ OSError: If the parent directory of `output_path` does not exist.
124
+ """
125
+ if not output_path.parent.exists():
126
+ raise OSError(f"Output directory does not exist: {output_path.parent}")
127
+
128
+ emitter.emit(
129
+ payload,
130
+ fmt=fmt,
131
+ pretty=pretty,
132
+ message="Audit dry-run completed" if dry_run else "Audit completed",
133
+ debug=debug,
134
+ output=str(output_path),
135
+ quiet=False,
136
+ )
137
+
138
+
139
+ @audit_app.callback(invoke_without_command=True)
140
+ def audit(
141
+ ctx: typer.Context,
142
+ dry_run: bool = DRY_RUN_OPTION,
143
+ output: Path | None = OUTPUT_OPTION,
144
+ quiet: bool = typer.Option(False, "-q", "--quiet", help=HELP_QUIET),
145
+ verbose: bool = typer.Option(False, "-v", "--verbose", help=HELP_VERBOSE),
146
+ fmt: str = typer.Option("json", "-f", "--format", help=HELP_FORMAT),
147
+ pretty: bool = typer.Option(True, "--pretty/--no-pretty", help=HELP_NO_PRETTY),
148
+ debug: bool = typer.Option(False, "-d", "--debug", help=HELP_DEBUG),
149
+ ) -> None:
150
+ """Defines the entrypoint and logic for the `bijux audit` command.
151
+
152
+ This function orchestrates the entire audit process. It validates all CLI
153
+ flags and arguments, performs environment checks (e.g., for non-ASCII
154
+ characters), builds the appropriate result payload, and emits it to
155
+ stdout or a file in the specified format. All errors are handled and
156
+ emitted in a structured format before exiting with a specific code.
157
+
158
+ Args:
159
+ ctx (typer.Context): The Typer context, used to manage command state
160
+ and detect stray arguments.
161
+ dry_run (bool): If True, simulates the audit and reports a "dry-run"
162
+ status without performing actions.
163
+ output (Path | None): If a path is provided, writes the audit result
164
+ to the specified file instead of stdout.
165
+ quiet (bool): If True, suppresses all output except for errors. The
166
+ exit code is the primary indicator of the outcome.
167
+ verbose (bool): If True, includes Python and platform details in the
168
+ output payload.
169
+ fmt (str): The output format, either "json" or "yaml". Defaults to "json".
170
+ pretty (bool): If True, pretty-prints the output for human readability.
171
+ This is overridden by `debug`.
172
+ debug (bool): If True, enables debug diagnostics, which implies `verbose`
173
+ and `pretty`.
174
+
175
+ Returns:
176
+ None:
177
+
178
+ Raises:
179
+ SystemExit: Exits with a status code and structured error payload upon
180
+ validation failures (e.g., bad arguments, ASCII errors), I/O
181
+ issues, or unexpected exceptions. The exit code follows the
182
+ contract defined in the module docstring.
183
+ """
184
+ if ctx.invoked_subcommand:
185
+ return
186
+
187
+ command = "audit"
188
+
189
+ try:
190
+ stray_args = [a for a in ctx.args if not a.startswith("-")]
191
+ if stray_args:
192
+ raise typer.BadParameter(f"No such argument: {stray_args[0]}")
193
+ fmt_lower = validate_common_flags(fmt, command, quiet)
194
+ include_runtime = verbose or debug
195
+ effective_pretty = debug or pretty
196
+ out_format = OutputFormat.YAML if fmt_lower == "yaml" else OutputFormat.JSON
197
+
198
+ if contains_non_ascii_env():
199
+ emit_error_and_exit(
200
+ "Non-ASCII environment variables detected",
201
+ code=3,
202
+ failure="ascii_env",
203
+ command=command,
204
+ fmt=fmt_lower,
205
+ quiet=quiet,
206
+ include_runtime=include_runtime,
207
+ )
208
+ try:
209
+ validate_env_file_if_present(os.environ.get("BIJUXCLI_CONFIG", ""))
210
+ except ValueError as exc:
211
+ emit_error_and_exit(
212
+ str(exc),
213
+ code=3,
214
+ failure="ascii",
215
+ command=command,
216
+ fmt=fmt_lower,
217
+ quiet=quiet,
218
+ include_runtime=include_runtime,
219
+ )
220
+
221
+ except typer.BadParameter as exc:
222
+ error_fmt = fmt.lower() if fmt.lower() in ("json", "yaml") else "json"
223
+ emit_error_and_exit(
224
+ exc.message,
225
+ code=2,
226
+ failure="args",
227
+ command=command,
228
+ fmt=error_fmt,
229
+ quiet=quiet,
230
+ include_runtime=verbose or debug,
231
+ )
232
+
233
+ try:
234
+ emitter = DIContainer.current().resolve(EmitterProtocol)
235
+ payload = _build_payload(include_runtime, dry_run)
236
+
237
+ if output is not None:
238
+ _write_output_file(
239
+ output_path=output,
240
+ payload=payload,
241
+ emitter=emitter,
242
+ fmt=out_format,
243
+ pretty=effective_pretty,
244
+ debug=debug,
245
+ dry_run=dry_run,
246
+ )
247
+ payload = {"status": "written", "file": str(output)}
248
+ if include_runtime:
249
+ payload["python"] = ascii_safe(
250
+ platform.python_version(), "python_version"
251
+ )
252
+ payload["platform"] = ascii_safe(platform.platform(), "platform")
253
+
254
+ new_run_command(
255
+ command_name=command,
256
+ payload_builder=lambda _: payload,
257
+ quiet=quiet,
258
+ verbose=(verbose or debug),
259
+ fmt=fmt_lower,
260
+ pretty=(debug or pretty),
261
+ debug=debug,
262
+ )
263
+
264
+ except ValueError as exc:
265
+ emit_error_and_exit(
266
+ str(exc),
267
+ code=3,
268
+ failure="ascii",
269
+ command=command,
270
+ fmt=fmt_lower,
271
+ quiet=quiet,
272
+ include_runtime=include_runtime,
273
+ )
274
+ except OSError as exc:
275
+ emit_error_and_exit(
276
+ str(exc),
277
+ code=2,
278
+ failure="output_file",
279
+ command=command,
280
+ fmt=fmt_lower,
281
+ quiet=quiet,
282
+ include_runtime=include_runtime,
283
+ )
284
+ except Exception as exc:
285
+ emit_error_and_exit(
286
+ f"An unexpected error occurred: {exc}",
287
+ code=1,
288
+ failure="unexpected",
289
+ command=command,
290
+ fmt=fmt_lower,
291
+ quiet=quiet,
292
+ include_runtime=include_runtime,
293
+ )
@@ -0,0 +1,85 @@
1
+ # SPDX-License-Identifier: MIT
2
+ # Copyright © 2025 Bijan Mousavi
3
+
4
+ """Defines the `config` command group for the Bijux CLI.
5
+
6
+ This module serves as the central entry point for all configuration management
7
+ functionality. It aggregates the various subcommands into a single `Typer`
8
+ application, creating the `bijux config` command hierarchy.
9
+
10
+ The `config` command, when run without a subcommand, lists all current
11
+ key-value pairs.
12
+
13
+ The available subcommands are:
14
+ * `set`: Sets a key-value pair.
15
+ * `get`: Retrieves the value for a specific key.
16
+ * `unset`: Removes a key-value pair.
17
+ * `list`: Lists all defined configuration keys.
18
+ * `clear`: Removes all key-value pairs from the configuration.
19
+ * `load`: Replaces the current configuration with one from a file.
20
+ * `reload`: Forces a reload of the configuration from its source file.
21
+ * `export`: Writes the current configuration to a file or stdout.
22
+
23
+ Exit codes for the subcommands generally follow this pattern:
24
+ * `0`: Success.
25
+ * `1`: An internal or unexpected error occurred.
26
+ * `2`: An invalid argument was provided (e.g., bad format, key not found).
27
+ * `3`: An ASCII or encoding error was detected.
28
+ """
29
+
30
+ from __future__ import annotations
31
+
32
+ import typer
33
+
34
+ from bijux_cli.commands.config.clear import clear_config
35
+ from bijux_cli.commands.config.export import export_config
36
+ from bijux_cli.commands.config.get import get_config
37
+ from bijux_cli.commands.config.list_cmd import list_config
38
+ from bijux_cli.commands.config.load import load_config
39
+ from bijux_cli.commands.config.reload import reload_config
40
+ from bijux_cli.commands.config.service import config
41
+ from bijux_cli.commands.config.set import set_config
42
+ from bijux_cli.commands.config.unset import unset_config
43
+
44
+ typer.core.rich = None # type: ignore[attr-defined,assignment]
45
+
46
+ config_app = typer.Typer( # pytype: skip-file
47
+ name="config",
48
+ help="Manage CLI configuration.",
49
+ rich_markup_mode=None,
50
+ context_settings={"help_option_names": ["-h", "--help"]},
51
+ no_args_is_help=False,
52
+ )
53
+
54
+ config_app.callback(invoke_without_command=True)(config)
55
+
56
+ config_app.command("clear")(clear_config)
57
+ config_app.command("export")(export_config)
58
+ config_app.command("get")(get_config)
59
+ config_app.command("list")(list_config)
60
+ config_app.command("load")(load_config)
61
+ config_app.command("reload")(reload_config)
62
+ config_app.command("set")(set_config)
63
+ config_app.command("unset")(unset_config)
64
+
65
+
66
+ @config_app.command("import", hidden=True)
67
+ def import_config(*args, **kwargs) -> None: # type: ignore
68
+ """Provides a backward-compatibility alias for the `config load` command.
69
+
70
+ This command is hidden from the main help text and delegates directly to
71
+ `load_config`, forwarding all arguments.
72
+
73
+ Args:
74
+ *args: Positional arguments to forward to `load_config`.
75
+ **kwargs: Keyword arguments to forward to `load_config`.
76
+
77
+ Returns:
78
+ None:
79
+ """
80
+ return load_config(*args, **kwargs)
81
+
82
+
83
+ __all__ = [
84
+ "config_app",
85
+ ]
@@ -0,0 +1,124 @@
1
+ # SPDX-License-Identifier: MIT
2
+ # Copyright © 2025 Bijan Mousavi
3
+
4
+ """Implements the `config clear` subcommand for the Bijux CLI.
5
+
6
+ This module contains the logic for completely erasing all key-value pairs from
7
+ the active configuration store. This action is irreversible and effectively
8
+ resets the configuration to an empty state. A structured confirmation is
9
+ emitted upon success.
10
+
11
+ Output Contract:
12
+ * Success: `{"status": "cleared"}`
13
+ * Verbose: Adds `{"python": str, "platform": str}` to the payload.
14
+ * Error: `{"error": str, "code": int}`
15
+
16
+ Exit Codes:
17
+ * `0`: Success.
18
+ * `1`: An unexpected error occurred while clearing the configuration.
19
+ """
20
+
21
+ from __future__ import annotations
22
+
23
+ import platform
24
+
25
+ import typer
26
+
27
+ from bijux_cli.commands.utilities import (
28
+ ascii_safe,
29
+ emit_error_and_exit,
30
+ new_run_command,
31
+ parse_global_flags,
32
+ )
33
+ from bijux_cli.contracts import ConfigProtocol
34
+ from bijux_cli.core.constants import (
35
+ HELP_DEBUG,
36
+ HELP_FORMAT,
37
+ HELP_NO_PRETTY,
38
+ HELP_QUIET,
39
+ HELP_VERBOSE,
40
+ )
41
+ from bijux_cli.core.di import DIContainer
42
+
43
+
44
+ def clear_config(
45
+ ctx: typer.Context,
46
+ quiet: bool = typer.Option(False, "-q", "--quiet", help=HELP_QUIET),
47
+ verbose: bool = typer.Option(False, "-v", "--verbose", help=HELP_VERBOSE),
48
+ fmt: str = typer.Option("json", "-f", "--format", help=HELP_FORMAT),
49
+ pretty: bool = typer.Option(True, "--pretty/--no-pretty", help=HELP_NO_PRETTY),
50
+ debug: bool = typer.Option(False, "-d", "--debug", help=HELP_DEBUG),
51
+ ) -> None:
52
+ """Clears all configuration settings from the active store.
53
+
54
+ This command erases all key-value pairs, effectively resetting the
55
+ configuration. It emits a structured payload to confirm the operation.
56
+
57
+ Args:
58
+ ctx (typer.Context): The Typer context for the CLI.
59
+ quiet (bool): If True, suppresses all output except for errors.
60
+ verbose (bool): If True, includes Python/platform details in the output.
61
+ fmt (str): The output format, "json" or "yaml".
62
+ pretty (bool): If True, pretty-prints the output.
63
+ debug (bool): If True, enables debug diagnostics.
64
+
65
+ Returns:
66
+ None:
67
+
68
+ Raises:
69
+ SystemExit: Always exits with a contract-compliant status code and
70
+ payload, indicating success or detailing the error.
71
+ """
72
+ flags = parse_global_flags()
73
+
74
+ quiet = flags["quiet"]
75
+ verbose = flags["verbose"]
76
+ fmt = flags["format"]
77
+ pretty = flags["pretty"]
78
+ debug = flags["debug"]
79
+
80
+ include_runtime = verbose
81
+ fmt_lower = fmt.lower()
82
+
83
+ command = "config clear"
84
+
85
+ config_svc = DIContainer.current().resolve(ConfigProtocol)
86
+
87
+ try:
88
+ config_svc.clear()
89
+ except Exception as exc:
90
+ emit_error_and_exit(
91
+ f"Failed to clear config: {exc}",
92
+ code=1,
93
+ failure="clear_failed",
94
+ command=command,
95
+ fmt=fmt_lower,
96
+ quiet=quiet,
97
+ include_runtime=include_runtime,
98
+ debug=debug,
99
+ )
100
+
101
+ def payload_builder(include_runtime: bool) -> dict[str, object]:
102
+ """Builds the payload confirming a successful configuration clear.
103
+
104
+ Args:
105
+ include_runtime (bool): If True, includes Python and platform info.
106
+
107
+ Returns:
108
+ dict[str, object]: The structured payload.
109
+ """
110
+ payload: dict[str, object] = {"status": "cleared"}
111
+ if include_runtime:
112
+ payload["python"] = ascii_safe(platform.python_version(), "python_version")
113
+ payload["platform"] = ascii_safe(platform.platform(), "platform")
114
+ return payload
115
+
116
+ new_run_command(
117
+ command_name=command,
118
+ payload_builder=payload_builder,
119
+ quiet=quiet,
120
+ verbose=verbose,
121
+ fmt=fmt_lower,
122
+ pretty=pretty,
123
+ debug=debug,
124
+ )
@@ -0,0 +1,148 @@
1
+ # SPDX-License-Identifier: MIT
2
+ # Copyright © 2025 Bijan Mousavi
3
+
4
+ """Implements the `config export` subcommand for the Bijux CLI.
5
+
6
+ This module contains the logic for exporting the application's entire current
7
+ configuration to a specified destination, which can be a file or standard
8
+ output. The output format can be explicitly set to 'env', 'json', or 'yaml',
9
+ or it can be inferred from the destination file's extension.
10
+
11
+ Output Contract:
12
+ * Success (to file): `{"status": "exported", "file": str, "format": str}`
13
+ * Success (to stdout): The raw exported configuration data is printed directly.
14
+ * Verbose (to file): Adds `{"python": str, "platform": str}` to the payload.
15
+ * Error: `{"error": str, "code": int}`
16
+
17
+ Exit Codes:
18
+ * `0`: Success.
19
+ * `1` or `2`: An error occurred during the export process, such as a file
20
+ write error or invalid format request.
21
+ """
22
+
23
+ from __future__ import annotations
24
+
25
+ import platform
26
+
27
+ import typer
28
+
29
+ from bijux_cli.commands.utilities import (
30
+ ascii_safe,
31
+ emit_error_and_exit,
32
+ new_run_command,
33
+ parse_global_flags,
34
+ )
35
+ from bijux_cli.contracts import ConfigProtocol
36
+ from bijux_cli.core.constants import (
37
+ HELP_DEBUG,
38
+ HELP_FORMAT,
39
+ HELP_NO_PRETTY,
40
+ HELP_QUIET,
41
+ HELP_VERBOSE,
42
+ )
43
+ from bijux_cli.core.di import DIContainer
44
+ from bijux_cli.core.exceptions import CommandError
45
+
46
+
47
+ def export_config(
48
+ ctx: typer.Context,
49
+ path: str = typer.Argument(
50
+ ..., help="Destination file – use “-” to write to STDOUT"
51
+ ),
52
+ out_fmt: str = typer.Option(
53
+ None, "--out-format", help="Force output format: env | json | yaml"
54
+ ),
55
+ quiet: bool = typer.Option(False, "-q", "--quiet", help=HELP_QUIET),
56
+ verbose: bool = typer.Option(False, "-v", "--verbose", help=HELP_VERBOSE),
57
+ fmt: str = typer.Option("json", "-f", "--format", help=HELP_FORMAT),
58
+ pretty: bool = typer.Option(True, "--pretty/--no-pretty", help=HELP_NO_PRETTY),
59
+ debug: bool = typer.Option(False, "-d", "--debug", help=HELP_DEBUG),
60
+ ) -> None:
61
+ """Exports the current configuration to a file or standard output.
62
+
63
+ This function writes all configuration key-value pairs to a specified
64
+ destination. If the destination is a file path, a structured JSON/YAML
65
+ confirmation message is printed to stdout upon success. If the destination
66
+ is "-", the raw exported configuration is printed directly to stdout.
67
+
68
+ Args:
69
+ ctx (typer.Context): The Typer context for the CLI.
70
+ path (str): The destination file path, or "-" for standard output.
71
+ out_fmt (str): The desired output format ('env', 'json', 'yaml'). If
72
+ unspecified, it is inferred from the file extension.
73
+ quiet (bool): If True, suppresses all output except for errors.
74
+ verbose (bool): If True, includes Python/platform details in the
75
+ confirmation payload (file export only).
76
+ fmt (str): The format for the confirmation payload ("json" or "yaml").
77
+ pretty (bool): If True, pretty-prints the confirmation payload.
78
+ debug (bool): If True, enables debug diagnostics.
79
+
80
+ Returns:
81
+ None:
82
+
83
+ Raises:
84
+ SystemExit: Always exits with a contract-compliant status code and
85
+ payload, indicating success or detailing the error.
86
+ """
87
+ flags = parse_global_flags()
88
+
89
+ quiet = flags["quiet"]
90
+ verbose = flags["verbose"]
91
+ fmt = flags["format"]
92
+ pretty = flags["pretty"]
93
+ debug = flags["debug"]
94
+
95
+ include_runtime = verbose
96
+ fmt_lower = fmt.lower()
97
+
98
+ command = "config export"
99
+
100
+ config_svc = DIContainer.current().resolve(ConfigProtocol)
101
+
102
+ try:
103
+ config_svc.export(path, out_fmt)
104
+ except CommandError as exc:
105
+ code = 2 if getattr(exc, "http_status", 0) == 400 else 1
106
+ emit_error_and_exit(
107
+ f"Failed to export config: {exc}",
108
+ code=code,
109
+ failure="export_failed",
110
+ command=command,
111
+ fmt=fmt_lower,
112
+ quiet=quiet,
113
+ include_runtime=include_runtime,
114
+ debug=debug,
115
+ )
116
+
117
+ if path != "-":
118
+
119
+ def payload_builder(include_runtime: bool) -> dict[str, object]:
120
+ """Builds the payload confirming a successful export to a file.
121
+
122
+ Args:
123
+ include_runtime (bool): If True, includes Python and platform info.
124
+
125
+ Returns:
126
+ dict[str, object]: The structured payload.
127
+ """
128
+ payload: dict[str, object] = {
129
+ "status": "exported",
130
+ "file": path,
131
+ "format": out_fmt or "auto",
132
+ }
133
+ if include_runtime:
134
+ payload["python"] = ascii_safe(
135
+ platform.python_version(), "python_version"
136
+ )
137
+ payload["platform"] = ascii_safe(platform.platform(), "platform")
138
+ return payload
139
+
140
+ new_run_command(
141
+ command_name=command,
142
+ payload_builder=payload_builder,
143
+ quiet=quiet,
144
+ verbose=verbose,
145
+ fmt=fmt_lower,
146
+ pretty=pretty,
147
+ debug=debug,
148
+ )