usecli 0.1.73__tar.gz → 0.1.75__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 (72) hide show
  1. {usecli-0.1.73 → usecli-0.1.75}/PKG-INFO +61 -3
  2. {usecli-0.1.73 → usecli-0.1.75}/README.md +60 -2
  3. {usecli-0.1.73 → usecli-0.1.75}/pyproject.toml +1 -1
  4. {usecli-0.1.73 → usecli-0.1.75}/src/usecli/__init__.py +136 -12
  5. {usecli-0.1.73 → usecli-0.1.75}/src/usecli/cli/commands/defaults/base/about_command.py +37 -20
  6. {usecli-0.1.73 → usecli-0.1.75}/src/usecli/cli/commands/defaults/base/inspire_command.py +10 -1
  7. {usecli-0.1.73 → usecli-0.1.75}/src/usecli/cli/commands/defaults/base/internal/fzf_command.py +2 -0
  8. {usecli-0.1.73 → usecli-0.1.75}/src/usecli/cli/commands/init_command.py +203 -6
  9. {usecli-0.1.73 → usecli-0.1.75}/src/usecli/cli/core/base_command.py +43 -2
  10. usecli-0.1.75/src/usecli/cli/core/runtime.py +97 -0
  11. {usecli-0.1.73 → usecli-0.1.75}/src/usecli/cli/core/ui/list.py +73 -26
  12. {usecli-0.1.73 → usecli-0.1.75}/src/usecli/cli/core/ui/title.py +4 -1
  13. {usecli-0.1.73 → usecli-0.1.75}/src/usecli/cli/templates/command.py.j2 +16 -1
  14. {usecli-0.1.73 → usecli-0.1.75}/src/usecli/cli/utils/interactive/terminal_menu.py +4 -0
  15. {usecli-0.1.73 → usecli-0.1.75}/src/usecli/menu.py +5 -0
  16. usecli-0.1.75/src/usecli/progress.py +169 -0
  17. {usecli-0.1.73 → usecli-0.1.75}/src/usecli/ui.py +7 -0
  18. {usecli-0.1.73 → usecli-0.1.75}/LICENSE +0 -0
  19. {usecli-0.1.73 → usecli-0.1.75}/src/usecli/cli/__init__.py +0 -0
  20. {usecli-0.1.73 → usecli-0.1.75}/src/usecli/cli/commands/README.md +0 -0
  21. {usecli-0.1.73 → usecli-0.1.75}/src/usecli/cli/commands/__init__.py +0 -0
  22. {usecli-0.1.73 → usecli-0.1.75}/src/usecli/cli/commands/custom/README.md +0 -0
  23. {usecli-0.1.73 → usecli-0.1.75}/src/usecli/cli/commands/custom/__init__.py +0 -0
  24. {usecli-0.1.73 → usecli-0.1.75}/src/usecli/cli/commands/defaults/__init__.py +0 -0
  25. {usecli-0.1.73 → usecli-0.1.75}/src/usecli/cli/commands/defaults/base/__init__.py +0 -0
  26. {usecli-0.1.73 → usecli-0.1.75}/src/usecli/cli/commands/defaults/base/help_command.py +0 -0
  27. {usecli-0.1.73 → usecli-0.1.75}/src/usecli/cli/commands/defaults/base/internal/__init__.py +0 -0
  28. {usecli-0.1.73 → usecli-0.1.75}/src/usecli/cli/commands/defaults/core/__init__.py +0 -0
  29. {usecli-0.1.73 → usecli-0.1.75}/src/usecli/cli/commands/defaults/core/utils.py +0 -0
  30. {usecli-0.1.73 → usecli-0.1.75}/src/usecli/cli/commands/defaults/make/__init__.py +0 -0
  31. {usecli-0.1.73 → usecli-0.1.75}/src/usecli/cli/commands/defaults/make/make_command.py +0 -0
  32. {usecli-0.1.73 → usecli-0.1.75}/src/usecli/cli/commands/defaults/make/make_theme_command.py +0 -0
  33. {usecli-0.1.73 → usecli-0.1.75}/src/usecli/cli/config/__init__.py +0 -0
  34. {usecli-0.1.73 → usecli-0.1.75}/src/usecli/cli/config/colors.py +0 -0
  35. {usecli-0.1.73 → usecli-0.1.75}/src/usecli/cli/core/__init__.py +0 -0
  36. {usecli-0.1.73 → usecli-0.1.75}/src/usecli/cli/core/error/__init__.py +0 -0
  37. {usecli-0.1.73 → usecli-0.1.75}/src/usecli/cli/core/error/handler.py +0 -0
  38. {usecli-0.1.73 → usecli-0.1.75}/src/usecli/cli/core/error/utils.py +0 -0
  39. {usecli-0.1.73 → usecli-0.1.75}/src/usecli/cli/core/exceptions/__init__.py +0 -0
  40. {usecli-0.1.73 → usecli-0.1.75}/src/usecli/cli/core/exceptions/base.py +0 -0
  41. {usecli-0.1.73 → usecli-0.1.75}/src/usecli/cli/core/exceptions/config.py +0 -0
  42. {usecli-0.1.73 → usecli-0.1.75}/src/usecli/cli/core/exceptions/usage.py +0 -0
  43. {usecli-0.1.73 → usecli-0.1.75}/src/usecli/cli/core/exceptions/validation.py +0 -0
  44. {usecli-0.1.73 → usecli-0.1.75}/src/usecli/cli/core/ui/__init__.py +0 -0
  45. {usecli-0.1.73 → usecli-0.1.75}/src/usecli/cli/core/ui/title.txt +0 -0
  46. {usecli-0.1.73 → usecli-0.1.75}/src/usecli/cli/core/validators/__init__.py +0 -0
  47. {usecli-0.1.73 → usecli-0.1.75}/src/usecli/cli/core/validators/network.py +0 -0
  48. {usecli-0.1.73 → usecli-0.1.75}/src/usecli/cli/core/validators/numeric.py +0 -0
  49. {usecli-0.1.73 → usecli-0.1.75}/src/usecli/cli/core/validators/path.py +0 -0
  50. {usecli-0.1.73 → usecli-0.1.75}/src/usecli/cli/core/validators/string.py +0 -0
  51. {usecli-0.1.73 → usecli-0.1.75}/src/usecli/cli/services/__init__.py +0 -0
  52. {usecli-0.1.73 → usecli-0.1.75}/src/usecli/cli/services/command_service.py +0 -0
  53. {usecli-0.1.73 → usecli-0.1.75}/src/usecli/cli/templates/theme.toml.j2 +0 -0
  54. {usecli-0.1.73 → usecli-0.1.75}/src/usecli/cli/templates/usecli.config.toml.j2 +0 -0
  55. {usecli-0.1.73 → usecli-0.1.75}/src/usecli/cli/themes/ayu_dark.toml +0 -0
  56. {usecli-0.1.73 → usecli-0.1.75}/src/usecli/cli/themes/catppuccin_frappe.toml +0 -0
  57. {usecli-0.1.73 → usecli-0.1.75}/src/usecli/cli/themes/catppuccin_latte.toml +0 -0
  58. {usecli-0.1.73 → usecli-0.1.75}/src/usecli/cli/themes/catppuccin_macchiato.toml +0 -0
  59. {usecli-0.1.73 → usecli-0.1.75}/src/usecli/cli/themes/catppuccin_mocha.toml +0 -0
  60. {usecli-0.1.73 → usecli-0.1.75}/src/usecli/cli/themes/default.toml +0 -0
  61. {usecli-0.1.73 → usecli-0.1.75}/src/usecli/cli/themes/dracula.toml +0 -0
  62. {usecli-0.1.73 → usecli-0.1.75}/src/usecli/cli/themes/gruvbox_dark.toml +0 -0
  63. {usecli-0.1.73 → usecli-0.1.75}/src/usecli/cli/themes/nord.toml +0 -0
  64. {usecli-0.1.73 → usecli-0.1.75}/src/usecli/cli/themes/tokyo_night.toml +0 -0
  65. {usecli-0.1.73 → usecli-0.1.75}/src/usecli/cli/utils/__init__.py +0 -0
  66. {usecli-0.1.73 → usecli-0.1.75}/src/usecli/cli/utils/interactive/__init__.py +0 -0
  67. {usecli-0.1.73 → usecli-0.1.75}/src/usecli/params.py +0 -0
  68. {usecli-0.1.73 → usecli-0.1.75}/src/usecli/shared/__init__.py +0 -0
  69. {usecli-0.1.73 → usecli-0.1.75}/src/usecli/shared/config/__init__.py +0 -0
  70. {usecli-0.1.73 → usecli-0.1.75}/src/usecli/shared/config/globals.py +0 -0
  71. {usecli-0.1.73 → usecli-0.1.75}/src/usecli/shared/config/manager.py +0 -0
  72. {usecli-0.1.73 → usecli-0.1.75}/src/usecli/usecli.config.toml +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: usecli
3
- Version: 0.1.73
3
+ Version: 0.1.75
4
4
  Summary: A powerful Python CLI framework for building beautiful, developer-friendly command-line tools.
5
5
  Author: Edward Boswell
6
6
  Author-email: Edward Boswell <thememium@gmail.com>
@@ -166,17 +166,75 @@ def signature(self) -> str:
166
166
  return "spec show" # usecli spec show
167
167
  ```
168
168
 
169
+ ### JSON Output
170
+
171
+ Every command inherits `--json`; commands do not declare the option themselves. The flag works before or after a command name:
172
+
173
+ ```sh
174
+ usecli --json about
175
+ usecli about --json
176
+ ```
177
+
178
+ A successful invocation writes exactly one JSON document to stdout:
179
+
180
+ ```json
181
+ {"ok":true,"data":null}
182
+ ```
183
+
184
+ A command can return any JSON-serializable value to populate `data`:
185
+
186
+ ```python
187
+ class StatusCommand(BaseCommand):
188
+ def signature(self) -> str:
189
+ return "status"
190
+
191
+ def description(self) -> str:
192
+ return "Show service status"
193
+
194
+ def handle(self):
195
+ return {"status": "ready", "workers": 3}
196
+ ```
197
+
198
+ Failures preserve a non-zero exit status and use a stable error envelope:
199
+
200
+ ```json
201
+ {"ok":false,"error":{"type":"UsageError","message":"No such command","code":2}}
202
+ ```
203
+
204
+ In JSON mode, stdout is reserved for that document. Human-facing output, including `console.print()` and ordinary `print()`, is routed to stderr. Prompts and confirmations resolve their declared defaults without reading stdin. A prompt without a default, menus, and `--interactive` fail immediately with a structured `NonInteractiveError` instead of blocking.
205
+
206
+ The existing fields `ok`, `data`, `error`, `error.type`, `error.message`, and `error.code` keep their names and types across compatible releases. New fields may be added.
207
+
169
208
  ### UI Components
170
209
 
171
210
  ```python
172
- from usecli import console, Prompt, Confirm, Menu
211
+ from usecli import Confirm, Menu, Prompt, console
173
212
 
174
213
  console.print("[green]Done![/green]")
175
214
  name = Prompt.ask("Enter name")
176
215
  ok = Confirm.ask("Continue?")
177
- choice = Menu(["A", "B", "C"]).show()
216
+ choice = Menu.select(["A", "B", "C"])
217
+ ```
218
+
219
+ ### Progress Indicators
220
+
221
+ Use `Spinner` when the amount of work is unknown and `ProgressBar` when a total is known:
222
+
223
+ ```python
224
+ from usecli import ProgressBar, Spinner
225
+
226
+ with Spinner("Loading records") as spinner:
227
+ load_records()
228
+ spinner.update("Indexing records")
229
+
230
+ with ProgressBar(total=len(items), description="Processing") as progress:
231
+ for item in items:
232
+ process(item)
233
+ progress.advance()
178
234
  ```
179
235
 
236
+ Progress always renders to stderr, uses the active usecli theme, and is suppressed automatically in JSON mode, with `quiet=True`, or when stderr is not attached to a terminal. Calling commands do not need to detect these conditions.
237
+
180
238
  ### Available Commands
181
239
 
182
240
  ```
@@ -138,17 +138,75 @@ def signature(self) -> str:
138
138
  return "spec show" # usecli spec show
139
139
  ```
140
140
 
141
+ ### JSON Output
142
+
143
+ Every command inherits `--json`; commands do not declare the option themselves. The flag works before or after a command name:
144
+
145
+ ```sh
146
+ usecli --json about
147
+ usecli about --json
148
+ ```
149
+
150
+ A successful invocation writes exactly one JSON document to stdout:
151
+
152
+ ```json
153
+ {"ok":true,"data":null}
154
+ ```
155
+
156
+ A command can return any JSON-serializable value to populate `data`:
157
+
158
+ ```python
159
+ class StatusCommand(BaseCommand):
160
+ def signature(self) -> str:
161
+ return "status"
162
+
163
+ def description(self) -> str:
164
+ return "Show service status"
165
+
166
+ def handle(self):
167
+ return {"status": "ready", "workers": 3}
168
+ ```
169
+
170
+ Failures preserve a non-zero exit status and use a stable error envelope:
171
+
172
+ ```json
173
+ {"ok":false,"error":{"type":"UsageError","message":"No such command","code":2}}
174
+ ```
175
+
176
+ In JSON mode, stdout is reserved for that document. Human-facing output, including `console.print()` and ordinary `print()`, is routed to stderr. Prompts and confirmations resolve their declared defaults without reading stdin. A prompt without a default, menus, and `--interactive` fail immediately with a structured `NonInteractiveError` instead of blocking.
177
+
178
+ The existing fields `ok`, `data`, `error`, `error.type`, `error.message`, and `error.code` keep their names and types across compatible releases. New fields may be added.
179
+
141
180
  ### UI Components
142
181
 
143
182
  ```python
144
- from usecli import console, Prompt, Confirm, Menu
183
+ from usecli import Confirm, Menu, Prompt, console
145
184
 
146
185
  console.print("[green]Done![/green]")
147
186
  name = Prompt.ask("Enter name")
148
187
  ok = Confirm.ask("Continue?")
149
- choice = Menu(["A", "B", "C"]).show()
188
+ choice = Menu.select(["A", "B", "C"])
189
+ ```
190
+
191
+ ### Progress Indicators
192
+
193
+ Use `Spinner` when the amount of work is unknown and `ProgressBar` when a total is known:
194
+
195
+ ```python
196
+ from usecli import ProgressBar, Spinner
197
+
198
+ with Spinner("Loading records") as spinner:
199
+ load_records()
200
+ spinner.update("Indexing records")
201
+
202
+ with ProgressBar(total=len(items), description="Processing") as progress:
203
+ for item in items:
204
+ process(item)
205
+ progress.advance()
150
206
  ```
151
207
 
208
+ Progress always renders to stderr, uses the active usecli theme, and is suppressed automatically in JSON mode, with `quiet=True`, or when stderr is not attached to a terminal. Calling commands do not need to detect these conditions.
209
+
152
210
  ### Available Commands
153
211
 
154
212
  ```
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "usecli"
3
- version = "0.1.73"
3
+ version = "0.1.75"
4
4
  description = "A powerful Python CLI framework for building beautiful, developer-friendly command-line tools."
5
5
  readme = "README.md"
6
6
  authors = [{ name = "Edward Boswell", email = "thememium@gmail.com" }]
@@ -20,6 +20,7 @@ if TYPE_CHECKING:
20
20
  from usecli.cli.core.base_command import BaseCommand
21
21
  from usecli.menu import Menu
22
22
  from usecli.params import Argument, Option
23
+ from usecli.progress import ProgressBar, Spinner
23
24
  from usecli.ui import Confirm, Prompt
24
25
 
25
26
  console: Console
@@ -37,6 +38,8 @@ _LAZY_EXPORTS = {
37
38
  "Confirm": ("usecli.ui", "Confirm"),
38
39
  "Console": ("usecli.ui", "Console"),
39
40
  "console": ("usecli.ui", "console"),
41
+ "Spinner": ("usecli.progress", "Spinner"),
42
+ "ProgressBar": ("usecli.progress", "ProgressBar"),
40
43
  }
41
44
 
42
45
 
@@ -155,18 +158,120 @@ def _ensure_cli_initialized() -> None:
155
158
  windows_expand_args=True,
156
159
  **extra,
157
160
  ):
158
- return super().main(
159
- args=args,
160
- prog_name=prog_name,
161
- complete_var=complete_var,
162
- standalone_mode=False,
163
- windows_expand_args=windows_expand_args,
164
- **extra,
161
+ cli_args = list(args) if args is not None else sys.argv[1:]
162
+ option_args = (
163
+ cli_args[: cli_args.index("--")] if "--" in cli_args else cli_args
165
164
  )
165
+ if "--json" not in option_args:
166
+ return super().main(
167
+ args=args,
168
+ prog_name=prog_name,
169
+ complete_var=complete_var,
170
+ standalone_mode=False,
171
+ windows_expand_args=windows_expand_args,
172
+ **extra,
173
+ )
174
+
175
+ from contextlib import redirect_stdout
176
+
177
+ from usecli.cli.core.runtime import (
178
+ InvocationExit,
179
+ JSONSerializationError,
180
+ NonInteractiveError,
181
+ error_document,
182
+ execution_context,
183
+ serialize_document,
184
+ success_document,
185
+ )
186
+
187
+ original_stdout = sys.stdout
188
+ _TyperBadParameter = globals()["_TyperBadParameter"]
189
+ _TyperUsageError = globals()["_TyperUsageError"]
190
+ _TyperClickException = globals()["_TyperClickException"]
191
+
192
+ def write_document(document):
193
+ original_stdout.write(serialize_document(document))
194
+ original_stdout.flush()
195
+
196
+ def fail(error_type: str, message: str, code: int):
197
+ write_document(
198
+ error_document(
199
+ error_type=error_type,
200
+ message=message,
201
+ code=code,
202
+ )
203
+ )
204
+ raise SystemExit(code)
205
+
206
+ with execution_context(json_mode=True):
207
+ try:
208
+ with redirect_stdout(sys.stderr):
209
+ result = super().main(
210
+ args=args,
211
+ prog_name=prog_name,
212
+ complete_var=complete_var,
213
+ standalone_mode=False,
214
+ windows_expand_args=windows_expand_args,
215
+ **extra,
216
+ )
217
+ write_document(success_document(result))
218
+ return result
219
+ except InvocationExit as error:
220
+ if error.code == 0:
221
+ write_document(success_document())
222
+ return None
223
+ fail("Exit", str(error), error.code)
224
+ except Exit as error:
225
+ code = error.exit_code
226
+ if code == 0:
227
+ write_document(success_document())
228
+ return None
229
+ fail("Exit", f"Command exited with status {code}", code)
230
+ except JSONSerializationError as error:
231
+ print(f"JSONSerializationError: {error}", file=sys.stderr)
232
+ fail("JSONSerializationError", str(error), 1)
233
+ except NonInteractiveError as error:
234
+ print(f"NonInteractiveError: {error}", file=sys.stderr)
235
+ fail("NonInteractiveError", str(error), error.code)
236
+ except (_TyperBadParameter, BadParameter) as error:
237
+ error.show(file=sys.stderr)
238
+ fail("BadParameter", error.format_message(), error.exit_code)
239
+ except (_TyperUsageError, UsageError) as error:
240
+ error.show(file=sys.stderr)
241
+ fail("UsageError", error.format_message(), error.exit_code)
242
+ except (_TyperClickException, ClickException) as error:
243
+ error.show(file=sys.stderr)
244
+ fail(
245
+ error.__class__.__name__,
246
+ error.format_message(),
247
+ error.exit_code,
248
+ )
249
+ except SystemExit as error:
250
+ code = error.code if isinstance(error.code, int) else 1
251
+ if code == 0:
252
+ write_document(success_document())
253
+ return None
254
+ fail("Exit", f"Command exited with status {code}", code)
255
+ except Exception as error:
256
+ print(
257
+ f"{error.__class__.__name__}: {error}",
258
+ file=sys.stderr,
259
+ )
260
+ fail(error.__class__.__name__, str(error), 1)
166
261
 
167
262
  def invoke(self, ctx):
168
263
  from click.exceptions import Exit
169
264
 
265
+ from usecli.cli.core.runtime import is_json_mode
266
+
267
+ if is_json_mode():
268
+ from usecli.cli.core.runtime import InvocationExit
269
+
270
+ try:
271
+ return super().invoke(ctx)
272
+ except Exit as error:
273
+ raise InvocationExit(error.exit_code) from error
274
+
170
275
  _TyperBadParameter = globals()["_TyperBadParameter"]
171
276
  _TyperUsageError = globals()["_TyperUsageError"]
172
277
  _TyperClickException = globals()["_TyperClickException"]
@@ -245,6 +350,8 @@ __all__ = [
245
350
  "Option",
246
351
  "Prompt",
247
352
  "Confirm",
353
+ "Spinner",
354
+ "ProgressBar",
248
355
  "colors",
249
356
  "theme",
250
357
  ]
@@ -356,12 +463,12 @@ class _FilteredListCommand:
356
463
  def invoke(self, ctx):
357
464
  from usecli.cli.core.ui.list import list_commands
358
465
 
359
- list_commands(_get_app(), prefix_filter=self.prefix_filter)
466
+ return list_commands(_get_app(), prefix_filter=self.prefix_filter)
360
467
 
361
468
  def __call__(self, *args, **kwargs):
362
469
  from usecli.cli.core.ui.list import list_commands
363
470
 
364
- list_commands(_get_app(), prefix_filter=self.prefix_filter)
471
+ return list_commands(_get_app(), prefix_filter=self.prefix_filter)
365
472
 
366
473
 
367
474
  def _get_default_help() -> str:
@@ -397,13 +504,23 @@ def _get_run_app_callback():
397
504
  interactive: bool = typer.Option(
398
505
  False, "--interactive", "-i", help="Run in interactive mode.", is_eager=True
399
506
  ),
400
- ) -> None:
507
+ json_output: bool = typer.Option(
508
+ False,
509
+ "--json",
510
+ help="Emit one machine-readable JSON document.",
511
+ is_eager=True,
512
+ ),
513
+ ) -> object:
401
514
  _resolve_help()
402
515
 
403
516
  if help:
404
517
  from usecli.cli.core.ui.list import list_commands
405
518
 
406
- list_commands(_get_app())
519
+ data = list_commands(_get_app())
520
+ from usecli.cli.core.runtime import is_json_mode
521
+
522
+ if is_json_mode():
523
+ return data
407
524
  raise typer.Exit()
408
525
 
409
526
  if version:
@@ -421,6 +538,13 @@ def _get_run_app_callback():
421
538
 
422
539
  interactive_requested = interactive or _is_interactive_flag_present()
423
540
 
541
+ from usecli.cli.core.runtime import is_json_mode
542
+
543
+ if interactive_requested and is_json_mode():
544
+ from usecli.cli.core.runtime import NonInteractiveError
545
+
546
+ raise NonInteractiveError("Interactive mode is unavailable in JSON mode")
547
+
424
548
  if interactive_requested:
425
549
  from usecli.cli.commands.defaults.base.internal.fzf_command import (
426
550
  run_interactive,
@@ -436,7 +560,7 @@ def _get_run_app_callback():
436
560
  prefix_filter = ctx.obj.get("prefix_filter")
437
561
  from usecli.cli.core.ui.list import list_commands
438
562
 
439
- list_commands(_get_app(), prefix_filter=prefix_filter)
563
+ return list_commands(_get_app(), prefix_filter=prefix_filter)
440
564
 
441
565
  globals()["run_app"] = run_app
442
566
  return run_app
@@ -257,7 +257,9 @@ class AboutCommand(BaseCommand):
257
257
  def description(self) -> str:
258
258
  return "Display detailed information about the application"
259
259
 
260
- def handle(self) -> None:
260
+ def handle(self) -> dict[str, object]:
261
+ import platform
262
+
261
263
  config = get_config()
262
264
  version = _get_application_version(config)
263
265
  from usecli.cli.core.ui.title import get_project_name
@@ -265,6 +267,38 @@ class AboutCommand(BaseCommand):
265
267
  app_name = get_project_name()
266
268
  description = _get_application_description(config)
267
269
 
270
+ dist = _get_application_distribution()
271
+ name_label = "Cli Name" if dist is not None else "Application Name"
272
+ version_label = "Cli Version" if dist is not None else "Application Version"
273
+
274
+ script_commands = _get_script_commands() or ["usecli"]
275
+
276
+ deps = _get_dependencies(config)
277
+ dependencies: list[dict[str, str | None]] = []
278
+ for dep_name, spec in deps:
279
+ try:
280
+ from importlib.metadata import version as get_version
281
+
282
+ installed_version = get_version(dep_name)
283
+ dependencies.append({"name": dep_name, "version": installed_version})
284
+ except Exception:
285
+ dependencies.append({"name": dep_name, "version": spec})
286
+
287
+ data: dict[str, object] = {
288
+ "name": app_name,
289
+ "version": version,
290
+ "description": description,
291
+ "python_version": platform.python_version(),
292
+ "platform": platform.platform(),
293
+ "entry_points": script_commands,
294
+ "dependencies": dependencies,
295
+ }
296
+
297
+ from usecli.cli.core.runtime import is_json_mode
298
+
299
+ if is_json_mode():
300
+ return data
301
+
268
302
  console.print()
269
303
  console.print(f"[bold {COLOR.PRIMARY}]Description[/bold {COLOR.PRIMARY}]")
270
304
  console.print(f"[{COLOR.PRIMARY}]─" * 78)
@@ -273,22 +307,14 @@ class AboutCommand(BaseCommand):
273
307
  console.print()
274
308
  console.print(f"[bold {COLOR.PRIMARY}]Environment[/bold {COLOR.PRIMARY}]")
275
309
  console.print(f"[{COLOR.PRIMARY}]─" * 78)
276
-
277
- dist = _get_application_distribution()
278
- name_label = "Cli Name" if dist is not None else "Application Name"
279
- version_label = "Cli Version" if dist is not None else "Application Version"
280
310
  self._print_row(name_label, app_name)
281
311
  self._print_row(version_label, version)
282
- import platform
283
-
284
312
  self._print_row("Python Version", platform.python_version())
285
313
  self._print_row("Platform", f"[{COLOR.FOREGROUND_MUTED}]{platform.platform()}")
286
314
 
287
315
  console.print()
288
316
  console.print(f"[bold {COLOR.PRIMARY}]Entry Points[/bold {COLOR.PRIMARY}]")
289
317
  console.print(f"[{COLOR.PRIMARY}]─" * 78)
290
-
291
- script_commands = _get_script_commands() or ["usecli"]
292
318
  for index, command in enumerate(script_commands):
293
319
  label = "Primary command" if index == 0 else "Command"
294
320
  self._print_row(command, label)
@@ -296,8 +322,6 @@ class AboutCommand(BaseCommand):
296
322
  console.print()
297
323
  console.print(f"[bold {COLOR.PRIMARY}]Dependencies[/bold {COLOR.PRIMARY}]")
298
324
  console.print(f"[{COLOR.PRIMARY}]─" * 78)
299
-
300
- deps = _get_dependencies(config)
301
325
  if deps:
302
326
  for dep_name, spec in deps:
303
327
  try:
@@ -313,17 +337,10 @@ class AboutCommand(BaseCommand):
313
337
  else:
314
338
  self._print_row("Dependencies", "unable to load")
315
339
 
316
- # console.print()
317
- # console.print(f"[bold {COLOR.PRIMARY}]Features[/bold {COLOR.PRIMARY}]")
318
- # console.print(f"[{COLOR.PRIMARY}]─" * 78)
319
- #
320
- # self._print_row("Prefix Matching", f"[{COLOR.SECONDARY}]ENABLED")
321
- # self._print_row("Rich UI", f"[{COLOR.SECONDARY}]ENABLED")
322
- # self._print_row("Command Scaffolding", f"[{COLOR.SECONDARY}]ENABLED")
323
- # self._print_row("Interactive Menus", f"[{COLOR.SECONDARY}]ENABLED")
324
- # self._print_row("Fuzzy Finder", f"[{COLOR.SECONDARY}]ENABLED")
325
340
  console.print()
326
341
 
342
+ return data
343
+
327
344
  def _print_row(self, label: str, value: str) -> None:
328
345
  visible_value = console.render_str(value).plain
329
346
  right_align_column = 76
@@ -24,7 +24,7 @@ class InspireCommand(BaseCommand):
24
24
  """Return the command description."""
25
25
  return "Show a random inspirational quote"
26
26
 
27
- def handle(self) -> None:
27
+ def handle(self) -> dict[str, str]:
28
28
  """Handle the command execution."""
29
29
 
30
30
  quotes = [
@@ -73,6 +73,13 @@ class InspireCommand(BaseCommand):
73
73
  selected = random.choice(quotes)
74
74
  quote, author = selected.rsplit(" - ", 1)
75
75
 
76
+ data = {"quote": quote, "author": author}
77
+
78
+ from usecli.cli.core.runtime import is_json_mode
79
+
80
+ if is_json_mode():
81
+ return data
82
+
76
83
  from rich.console import Console
77
84
  from rich.panel import Panel
78
85
 
@@ -84,3 +91,5 @@ class InspireCommand(BaseCommand):
84
91
  title_align="left",
85
92
  )
86
93
  )
94
+
95
+ return data
@@ -90,6 +90,8 @@ def _get_optional_options(
90
90
  continue
91
91
  if "--interactive" in option_names or "-i" in option_names:
92
92
  continue
93
+ if "--json" in option_names:
94
+ continue
93
95
 
94
96
  help_text = param.help or ""
95
97
  param_name = param.name or ""