usecli 0.1.76__tar.gz → 0.1.77__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 (73) hide show
  1. {usecli-0.1.76 → usecli-0.1.77}/PKG-INFO +7 -7
  2. {usecli-0.1.76 → usecli-0.1.77}/README.md +1 -1
  3. {usecli-0.1.76 → usecli-0.1.77}/pyproject.toml +22 -12
  4. {usecli-0.1.76 → usecli-0.1.77}/pyproject.toml.orig +20 -12
  5. {usecli-0.1.76 → usecli-0.1.77}/src/usecli/__init__.py +53 -26
  6. {usecli-0.1.76 → usecli-0.1.77}/src/usecli/cli/commands/defaults/base/about_command.py +5 -6
  7. {usecli-0.1.76 → usecli-0.1.77}/src/usecli/cli/commands/defaults/base/internal/fzf_command.py +24 -20
  8. {usecli-0.1.76 → usecli-0.1.77}/src/usecli/cli/commands/defaults/core/utils.py +2 -6
  9. {usecli-0.1.76 → usecli-0.1.77}/src/usecli/cli/commands/defaults/make/make_command.py +1 -3
  10. {usecli-0.1.76 → usecli-0.1.77}/src/usecli/cli/commands/defaults/make/make_theme_command.py +1 -2
  11. {usecli-0.1.76 → usecli-0.1.77}/src/usecli/cli/commands/init_command.py +4 -3
  12. {usecli-0.1.76 → usecli-0.1.77}/src/usecli/cli/config/colors.py +8 -5
  13. {usecli-0.1.76 → usecli-0.1.77}/src/usecli/cli/core/base_command.py +19 -20
  14. {usecli-0.1.76 → usecli-0.1.77}/src/usecli/cli/core/error/handler.py +4 -3
  15. {usecli-0.1.76 → usecli-0.1.77}/src/usecli/cli/core/exceptions/validation.py +2 -2
  16. {usecli-0.1.76 → usecli-0.1.77}/src/usecli/cli/core/ui/list.py +4 -6
  17. {usecli-0.1.76 → usecli-0.1.77}/src/usecli/cli/core/ui/title.py +4 -5
  18. {usecli-0.1.76 → usecli-0.1.77}/src/usecli/cli/services/command_service.py +0 -4
  19. {usecli-0.1.76 → usecli-0.1.77}/src/usecli/cli/utils/interactive/terminal_menu.py +5 -4
  20. {usecli-0.1.76 → usecli-0.1.77}/src/usecli/params.py +7 -6
  21. {usecli-0.1.76 → usecli-0.1.77}/src/usecli/progress.py +7 -3
  22. {usecli-0.1.76 → usecli-0.1.77}/src/usecli/shared/config/manager.py +24 -19
  23. {usecli-0.1.76 → usecli-0.1.77}/LICENSE +0 -0
  24. {usecli-0.1.76 → usecli-0.1.77}/src/usecli/cli/__init__.py +0 -0
  25. {usecli-0.1.76 → usecli-0.1.77}/src/usecli/cli/commands/README.md +0 -0
  26. {usecli-0.1.76 → usecli-0.1.77}/src/usecli/cli/commands/__init__.py +0 -0
  27. {usecli-0.1.76 → usecli-0.1.77}/src/usecli/cli/commands/custom/README.md +0 -0
  28. {usecli-0.1.76 → usecli-0.1.77}/src/usecli/cli/commands/custom/__init__.py +0 -0
  29. {usecli-0.1.76 → usecli-0.1.77}/src/usecli/cli/commands/defaults/__init__.py +0 -0
  30. {usecli-0.1.76 → usecli-0.1.77}/src/usecli/cli/commands/defaults/base/__init__.py +0 -0
  31. {usecli-0.1.76 → usecli-0.1.77}/src/usecli/cli/commands/defaults/base/help_command.py +0 -0
  32. {usecli-0.1.76 → usecli-0.1.77}/src/usecli/cli/commands/defaults/base/inspire_command.py +0 -0
  33. {usecli-0.1.76 → usecli-0.1.77}/src/usecli/cli/commands/defaults/base/internal/__init__.py +0 -0
  34. {usecli-0.1.76 → usecli-0.1.77}/src/usecli/cli/commands/defaults/core/__init__.py +0 -0
  35. {usecli-0.1.76 → usecli-0.1.77}/src/usecli/cli/commands/defaults/make/__init__.py +0 -0
  36. {usecli-0.1.76 → usecli-0.1.77}/src/usecli/cli/config/__init__.py +0 -0
  37. {usecli-0.1.76 → usecli-0.1.77}/src/usecli/cli/core/__init__.py +9 -9
  38. {usecli-0.1.76 → usecli-0.1.77}/src/usecli/cli/core/error/__init__.py +1 -1
  39. {usecli-0.1.76 → usecli-0.1.77}/src/usecli/cli/core/error/utils.py +0 -0
  40. {usecli-0.1.76 → usecli-0.1.77}/src/usecli/cli/core/exceptions/__init__.py +2 -2
  41. {usecli-0.1.76 → usecli-0.1.77}/src/usecli/cli/core/exceptions/base.py +0 -0
  42. {usecli-0.1.76 → usecli-0.1.77}/src/usecli/cli/core/exceptions/config.py +0 -0
  43. {usecli-0.1.76 → usecli-0.1.77}/src/usecli/cli/core/exceptions/usage.py +0 -0
  44. {usecli-0.1.76 → usecli-0.1.77}/src/usecli/cli/core/runtime.py +0 -0
  45. {usecli-0.1.76 → usecli-0.1.77}/src/usecli/cli/core/ui/__init__.py +1 -1
  46. {usecli-0.1.76 → usecli-0.1.77}/src/usecli/cli/core/ui/title.txt +0 -0
  47. {usecli-0.1.76 → usecli-0.1.77}/src/usecli/cli/core/validators/__init__.py +5 -5
  48. {usecli-0.1.76 → usecli-0.1.77}/src/usecli/cli/core/validators/network.py +0 -0
  49. {usecli-0.1.76 → usecli-0.1.77}/src/usecli/cli/core/validators/numeric.py +0 -0
  50. {usecli-0.1.76 → usecli-0.1.77}/src/usecli/cli/core/validators/path.py +0 -0
  51. {usecli-0.1.76 → usecli-0.1.77}/src/usecli/cli/core/validators/string.py +0 -0
  52. {usecli-0.1.76 → usecli-0.1.77}/src/usecli/cli/services/__init__.py +0 -0
  53. {usecli-0.1.76 → usecli-0.1.77}/src/usecli/cli/templates/command.py.j2 +0 -0
  54. {usecli-0.1.76 → usecli-0.1.77}/src/usecli/cli/templates/theme.toml.j2 +0 -0
  55. {usecli-0.1.76 → usecli-0.1.77}/src/usecli/cli/templates/usecli.config.toml.j2 +0 -0
  56. {usecli-0.1.76 → usecli-0.1.77}/src/usecli/cli/themes/ayu_dark.toml +0 -0
  57. {usecli-0.1.76 → usecli-0.1.77}/src/usecli/cli/themes/catppuccin_frappe.toml +0 -0
  58. {usecli-0.1.76 → usecli-0.1.77}/src/usecli/cli/themes/catppuccin_latte.toml +0 -0
  59. {usecli-0.1.76 → usecli-0.1.77}/src/usecli/cli/themes/catppuccin_macchiato.toml +0 -0
  60. {usecli-0.1.76 → usecli-0.1.77}/src/usecli/cli/themes/catppuccin_mocha.toml +0 -0
  61. {usecli-0.1.76 → usecli-0.1.77}/src/usecli/cli/themes/default.toml +0 -0
  62. {usecli-0.1.76 → usecli-0.1.77}/src/usecli/cli/themes/dracula.toml +0 -0
  63. {usecli-0.1.76 → usecli-0.1.77}/src/usecli/cli/themes/gruvbox_dark.toml +0 -0
  64. {usecli-0.1.76 → usecli-0.1.77}/src/usecli/cli/themes/nord.toml +0 -0
  65. {usecli-0.1.76 → usecli-0.1.77}/src/usecli/cli/themes/tokyo_night.toml +0 -0
  66. {usecli-0.1.76 → usecli-0.1.77}/src/usecli/cli/utils/__init__.py +0 -0
  67. {usecli-0.1.76 → usecli-0.1.77}/src/usecli/cli/utils/interactive/__init__.py +0 -0
  68. {usecli-0.1.76 → usecli-0.1.77}/src/usecli/menu.py +0 -0
  69. {usecli-0.1.76 → usecli-0.1.77}/src/usecli/shared/__init__.py +0 -0
  70. {usecli-0.1.76 → usecli-0.1.77}/src/usecli/shared/config/__init__.py +0 -0
  71. {usecli-0.1.76 → usecli-0.1.77}/src/usecli/shared/config/globals.py +0 -0
  72. {usecli-0.1.76 → usecli-0.1.77}/src/usecli/ui.py +4 -4
  73. {usecli-0.1.76 → usecli-0.1.77}/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.76
3
+ Version: 0.1.77
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>
@@ -10,14 +10,14 @@ Classifier: Programming Language :: Python :: 3
10
10
  Classifier: Operating System :: OS Independent
11
11
  Classifier: Intended Audience :: Developers
12
12
  Requires-Dist: case-converter>=1.2.0
13
- Requires-Dist: click>=8.3.1
14
- Requires-Dist: fzf-bin>=0.67.0
13
+ Requires-Dist: click>=8.4.2
14
+ Requires-Dist: fzf-bin>=0.74.1
15
15
  Requires-Dist: jinja2>=3.1.6
16
16
  Requires-Dist: pyfiglet>=1.0.4
17
- Requires-Dist: rich>=14.3.1
17
+ Requires-Dist: rich>=15.0.0
18
18
  Requires-Dist: simple-term-menu>=1.6.6
19
- Requires-Dist: tomli>=2.4.0
20
- Requires-Dist: typer>=0.21.1
19
+ Requires-Dist: tomli>=2.4.1
20
+ Requires-Dist: typer>=0.27.0
21
21
  Requires-Python: >=3.10
22
22
  Project-URL: Homepage, https://github.com/thememium/usecli
23
23
  Project-URL: Documentation, https://github.com/thememium/usecli
@@ -163,7 +163,7 @@ Space-separated signatures create nested subcommands:
163
163
 
164
164
  ```python
165
165
  def signature(self) -> str:
166
- return "spec show" # usecli spec show
166
+ return "spec show" # usecli spec show
167
167
  ```
168
168
 
169
169
  ### JSON Output
@@ -135,7 +135,7 @@ Space-separated signatures create nested subcommands:
135
135
 
136
136
  ```python
137
137
  def signature(self) -> str:
138
- return "spec show" # usecli spec show
138
+ return "spec show" # usecli spec show
139
139
  ```
140
140
 
141
141
  ### JSON Output
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "usecli"
3
- version = "0.1.76"
3
+ version = "0.1.77"
4
4
  description = "A powerful Python CLI framework for building beautiful, developer-friendly command-line tools."
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.10"
@@ -11,14 +11,14 @@ classifiers = [
11
11
  ]
12
12
  dependencies = [
13
13
  "case-converter>=1.2.0",
14
- "click>=8.3.1",
15
- "fzf-bin>=0.67.0",
14
+ "click>=8.4.2",
15
+ "fzf-bin>=0.74.1",
16
16
  "jinja2>=3.1.6",
17
17
  "pyfiglet>=1.0.4",
18
- "rich>=14.3.1",
18
+ "rich>=15.0.0",
19
19
  "simple-term-menu>=1.6.6",
20
- "tomli>=2.4.0",
21
- "typer>=0.21.1",
20
+ "tomli>=2.4.1",
21
+ "typer>=0.27.0",
22
22
  ]
23
23
  license = "MIT"
24
24
  license-files = ["LICEN[CS]E*"]
@@ -36,6 +36,7 @@ Changelog = "https://github.com/thememium/usecli/blob/master/CHANGELOG.md"
36
36
 
37
37
  [project.scripts]
38
38
  usecli = "usecli:main"
39
+ INVALID = "usecli:main"
39
40
 
40
41
  [build-system]
41
42
  requires = ["uv_build>=0.10.2,<0.11.0"]
@@ -43,13 +44,13 @@ build-backend = "uv_build"
43
44
 
44
45
  [dependency-groups]
45
46
  dev = [
46
- "deptry>=0.24.0",
47
- "isort>=7.0.0",
48
- "poethepoet>=0.41.0",
49
- "pytest>=9.0.2",
47
+ "deptry>=0.25.1",
48
+ "isort>=8.0.1",
49
+ "poethepoet>=0.48.0",
50
+ "pytest>=9.1.1",
50
51
  "pytest-cov>=7.1.0",
51
- "ruff>=0.15.1",
52
- "ty>=0.0.17",
52
+ "ruff>=0.16.1",
53
+ "ty>=0.0.65",
53
54
  ]
54
55
 
55
56
  [tool.deptry]
@@ -75,6 +76,9 @@ typecheck = "uv run ty check"
75
76
  build = "uv build --no-sources"
76
77
  release = "bash scripts/release.sh"
77
78
 
79
+ [tool.ty.environment]
80
+ python-version = "3.12"
81
+
78
82
  [tool.ty.src]
79
83
  include = [
80
84
  "src",
@@ -90,3 +94,9 @@ exclude = [
90
94
  [tool.setuptools.packages.find]
91
95
  where = ["."]
92
96
  include = ["cli*"]
97
+
98
+ [tool.setuptools.package-data]
99
+ cli = [
100
+ "**/*.toml",
101
+ "**/*.j2",
102
+ ]
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "usecli"
3
- version = "0.1.76"
3
+ version = "0.1.77"
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" }]
@@ -12,14 +12,14 @@ classifiers = [
12
12
  ]
13
13
  dependencies = [
14
14
  "case-converter>=1.2.0",
15
- "click>=8.3.1",
16
- "fzf-bin>=0.67.0",
15
+ "click>=8.4.2",
16
+ "fzf-bin>=0.74.1",
17
17
  "jinja2>=3.1.6",
18
18
  "pyfiglet>=1.0.4",
19
- "rich>=14.3.1",
19
+ "rich>=15.0.0",
20
20
  "simple-term-menu>=1.6.6",
21
- "tomli>=2.4.0",
22
- "typer>=0.21.1",
21
+ "tomli>=2.4.1",
22
+ "typer>=0.27.0",
23
23
  ]
24
24
  license = "MIT"
25
25
  license-files = ["LICEN[CS]E*"]
@@ -33,6 +33,7 @@ Changelog = "https://github.com/thememium/usecli/blob/master/CHANGELOG.md"
33
33
 
34
34
  [project.scripts]
35
35
  usecli = "usecli:main"
36
+ INVALID = "usecli:main"
36
37
 
37
38
  [build-system]
38
39
  requires = ["uv_build>=0.10.2,<0.11.0"]
@@ -40,13 +41,13 @@ build-backend = "uv_build"
40
41
 
41
42
  [dependency-groups]
42
43
  dev = [
43
- "deptry>=0.24.0",
44
- "isort>=7.0.0",
45
- "poethepoet>=0.41.0",
46
- "pytest>=9.0.2",
44
+ "deptry>=0.25.1",
45
+ "isort>=8.0.1",
46
+ "poethepoet>=0.48.0",
47
+ "pytest>=9.1.1",
47
48
  "pytest-cov>=7.1.0",
48
- "ruff>=0.15.1",
49
- "ty>=0.0.17",
49
+ "ruff>=0.16.1",
50
+ "ty>=0.0.65",
50
51
  ]
51
52
 
52
53
  [tool.deptry]
@@ -74,6 +75,9 @@ release = "bash scripts/release.sh"
74
75
 
75
76
  [tool.ty]
76
77
 
78
+ [tool.ty.environment]
79
+ python-version = "3.12"
80
+
77
81
  [tool.ty.src]
78
82
  include = ["src", "tests"]
79
83
  exclude = ["**/__pycache__", "**/.venv"]
@@ -85,3 +89,7 @@ exclude = ["**/__pycache__", "**/.venv"]
85
89
  [tool.setuptools.packages.find]
86
90
  where = ["."]
87
91
  include = ["cli*"]
92
+
93
+ [tool.setuptools.package-data]
94
+ cli = ["**/*.toml", "**/*.j2"]
95
+
@@ -5,6 +5,8 @@ from __future__ import annotations
5
5
  import sys
6
6
  from importlib import import_module
7
7
 
8
+ from click import Command as _ClickCommand
9
+
8
10
  # Avoid importing typing at module level (costs ~6ms)
9
11
  # TYPE_CHECKING is False at runtime, so the if-block never executes
10
12
  TYPE_CHECKING = False
@@ -84,11 +86,14 @@ def _ensure_cli_initialized() -> None:
84
86
  from usecli.cli.services.command_service import CommandService
85
87
 
86
88
  # Store exceptions for error handling
89
+ # Typer vendors click internally and raises its own exception types.
90
+ # We import them dynamically since typer._click has no type stubs.
87
91
  try:
88
- from typer._click.exceptions import BadParameter as TyperBadParameter # type: ignore[import-untyped]
89
- from typer._click.exceptions import ClickException as TyperClickException # type: ignore[import-untyped]
90
- from typer._click.exceptions import UsageError as TyperUsageError # type: ignore[import-untyped]
91
- except ImportError:
92
+ _typer_exceptions = import_module("typer._click.exceptions")
93
+ TyperBadParameter: Any = _typer_exceptions.BadParameter
94
+ TyperClickException: Any = _typer_exceptions.ClickException
95
+ TyperUsageError: Any = _typer_exceptions.UsageError
96
+ except (ImportError, AttributeError):
92
97
  TyperBadParameter = BadParameter
93
98
  TyperClickException = ClickException
94
99
  TyperUsageError = UsageError
@@ -138,7 +143,7 @@ def _ensure_cli_initialized() -> None:
138
143
  return None
139
144
 
140
145
  if cmd_name in matches:
141
- if (
146
+ if ( # pragma: no cover - unreachable duplicate of the branch above
142
147
  cmd_name in group_alias_to_primary
143
148
  and group_alias_to_primary[cmd_name] != cmd_name
144
149
  ):
@@ -252,7 +257,14 @@ def _ensure_cli_initialized() -> None:
252
257
  write_document(success_document())
253
258
  return None
254
259
  fail("Exit", f"Command exited with status {code}", code)
255
- except Exception as error:
260
+ except (
261
+ OSError,
262
+ RuntimeError,
263
+ ValueError,
264
+ TypeError,
265
+ AttributeError,
266
+ KeyError,
267
+ ) as error:
256
268
  print(
257
269
  f"{error.__class__.__name__}: {error}",
258
270
  file=sys.stderr,
@@ -340,19 +352,19 @@ def _get_service():
340
352
 
341
353
 
342
354
  __all__ = [
343
- "BaseCommand",
344
355
  "COLOR",
345
- "console",
356
+ "Argument",
357
+ "BaseCommand",
358
+ "Confirm",
346
359
  "Console",
347
- "main",
348
360
  "Menu",
349
- "Argument",
350
361
  "Option",
362
+ "ProgressBar",
351
363
  "Prompt",
352
- "Confirm",
353
364
  "Spinner",
354
- "ProgressBar",
355
365
  "colors",
366
+ "console",
367
+ "main",
356
368
  "theme",
357
369
  ]
358
370
 
@@ -432,32 +444,40 @@ def _build_alias_to_primary(alias_registry: dict[str, list[str]]) -> dict[str, s
432
444
  PrefixMatchingGroup = None
433
445
 
434
446
 
435
- class _FilteredListCommand:
447
+ class _FilteredListCommand(_ClickCommand):
436
448
  """Command that displays a filtered list of commands."""
437
449
 
438
450
  def __init__(self, prefix_filter: str) -> None:
451
+ super().__init__(
452
+ name="filtered-list",
453
+ context_settings={},
454
+ params=[],
455
+ callback=None,
456
+ help=None,
457
+ epilog=None,
458
+ short_help=None,
459
+ no_args_is_help=False,
460
+ hidden=False,
461
+ deprecated=False,
462
+ )
439
463
  self.prefix_filter = prefix_filter
440
- self.name = "filtered-list"
441
464
  self.allow_extra_args = True
442
465
  self.allow_interspersed_args = True
443
466
  self.ignore_unknown_options = True
444
- self.hidden = False
445
- self.deprecated = False
446
- self.params = []
447
- self.callback = None
448
- self.help = None
449
- self.epilog = None
450
- self.short_help = None
451
- self.no_args_is_help = False
452
- self.context_settings = {}
453
467
 
454
468
  def get_short_help_str(self, limit=45):
455
469
  return ""
456
470
 
457
- def make_context(self, info_name: str, args: list[str], **kwargs: object):
471
+ def make_context(
472
+ self,
473
+ info_name: str | None,
474
+ args: list[str],
475
+ parent: object | None = None,
476
+ **extra: Any,
477
+ ):
458
478
  from click import Context
459
479
 
460
- ctx = Context(self, info_name=info_name, **kwargs) # type: ignore[arg-type]
480
+ ctx = Context(self, info_name=info_name, **extra)
461
481
  return ctx
462
482
 
463
483
  def invoke(self, ctx):
@@ -615,7 +635,14 @@ def main() -> None:
615
635
  if hasattr(e, "show"):
616
636
  e.show()
617
637
  sys.exit(e.exit_code if hasattr(e, "exit_code") else 1)
618
- except Exception as e:
638
+ except (
639
+ OSError,
640
+ RuntimeError,
641
+ ValueError,
642
+ TypeError,
643
+ AttributeError,
644
+ KeyError,
645
+ ) as e:
619
646
  from usecli.cli.core.exceptions import UsecliUsageError
620
647
 
621
648
  styled_error = UsecliUsageError(str(e))
@@ -2,6 +2,7 @@ from __future__ import annotations
2
2
 
3
3
  import os
4
4
  import sys
5
+ from importlib.metadata import PackageNotFoundError
5
6
  from pathlib import Path
6
7
 
7
8
  from usecli.cli.config.colors import COLOR
@@ -200,7 +201,7 @@ def _get_installed_script_commands(command_name: str | None) -> list[str]:
200
201
  return []
201
202
  try:
202
203
  entry_points = dist.entry_points
203
- except Exception:
204
+ except (AttributeError, OSError):
204
205
  return []
205
206
  script_names = [
206
207
  entry_point.name
@@ -242,9 +243,7 @@ def _get_script_commands() -> list[str]:
242
243
  return [primary_command]
243
244
  return []
244
245
 
245
- script_names = [
246
- name for name in scripts.keys() if isinstance(name, str) and name.strip()
247
- ]
246
+ script_names = [name for name in scripts if isinstance(name, str) and name.strip()]
248
247
  if primary_command and primary_command not in script_names:
249
248
  return [primary_command, *script_names]
250
249
  return script_names
@@ -281,7 +280,7 @@ class AboutCommand(BaseCommand):
281
280
 
282
281
  installed_version = get_version(dep_name)
283
282
  dependencies.append({"name": dep_name, "version": installed_version})
284
- except Exception:
283
+ except (PackageNotFoundError, OSError):
285
284
  dependencies.append({"name": dep_name, "version": spec})
286
285
 
287
286
  data: dict[str, object] = {
@@ -329,7 +328,7 @@ class AboutCommand(BaseCommand):
329
328
 
330
329
  installed_version = get_version(dep_name)
331
330
  self._print_row(dep_name, installed_version)
332
- except Exception:
331
+ except (PackageNotFoundError, OSError):
333
332
  if spec:
334
333
  self._print_row(dep_name, spec)
335
334
  else:
@@ -8,7 +8,7 @@ import shutil
8
8
  import subprocess
9
9
  import sys
10
10
  from pathlib import Path
11
- from typing import TYPE_CHECKING, Any
11
+ from typing import TYPE_CHECKING, Any, cast
12
12
 
13
13
  import click
14
14
  import typer
@@ -41,16 +41,18 @@ def _get_required_arguments(command: ClickCommand) -> list[tuple[str, str, type]
41
41
 
42
42
  is_required = False
43
43
 
44
- if param.default is inspect.Parameter.empty:
45
- is_required = True
46
- elif hasattr(param.default, "default") and param.default.default is ...:
44
+ if (
45
+ param.default is inspect.Parameter.empty
46
+ or hasattr(param.default, "default")
47
+ and param.default.default is ...
48
+ ):
47
49
  is_required = True
48
50
 
49
51
  if is_required:
50
52
  help_text = ""
51
53
  try:
52
54
  help_text = getattr(param.default, "help", "") or ""
53
- except AttributeError:
55
+ except AttributeError: # pragma: no cover - getattr supplies a default
54
56
  pass
55
57
 
56
58
  param_type: type = param.annotation
@@ -65,14 +67,17 @@ def _get_required_arguments(command: ClickCommand) -> list[tuple[str, str, type]
65
67
  def _get_optional_options(
66
68
  command: ClickCommand | typer.models.CommandInfo,
67
69
  ) -> list[tuple[str, str, str, type]]:
68
- click_command: click.Command | None
70
+ click_command: click.Command | None = None
69
71
  if isinstance(command, click.Command):
70
72
  click_command = command
71
73
  elif isinstance(command, typer.models.CommandInfo):
72
- click_command = typer.main.get_command_from_info(
73
- command,
74
- pretty_exceptions_short=False,
75
- rich_markup_mode=None,
74
+ click_command = cast(
75
+ click.Command,
76
+ typer.main.get_command_from_info(
77
+ command,
78
+ pretty_exceptions_short=False,
79
+ rich_markup_mode=None,
80
+ ),
76
81
  )
77
82
  else:
78
83
  click_command = None
@@ -137,6 +142,7 @@ def _run_fzf_menu(
137
142
  input=input_data,
138
143
  capture_output=True,
139
144
  text=True,
145
+ check=False,
140
146
  )
141
147
 
142
148
  if result.returncode == 130 or not result.stdout.strip():
@@ -346,6 +352,7 @@ def run_interactive(
346
352
  capture_output=True,
347
353
  text=True,
348
354
  env={**os.environ, "FORCE_COLOR": "1", "CLICOLOR_FORCE": "1"},
355
+ check=False,
349
356
  )
350
357
  sys.stdout.write(help_result.stdout)
351
358
 
@@ -451,7 +458,7 @@ def run_interactive(
451
458
  for selected_option in selected_options:
452
459
  idx = menu_options.index(selected_option)
453
460
  (
454
- param_name,
461
+ _param_name,
455
462
  option_names,
456
463
  help_text,
457
464
  opt_type,
@@ -520,13 +527,12 @@ def run_interactive(
520
527
  console.print(f"[bold {COLOR.WARNING}]{full_cmd}")
521
528
  console.print()
522
529
 
523
- subprocess.run(full_cmd, shell=True)
530
+ subprocess.run(full_cmd, shell=True, check=False)
524
531
 
525
- try:
526
- pass
527
- except KeyboardInterrupt:
528
- ErrorHandler.display_warning("Cancelled by user")
529
- raise typer.Exit(code=0)
532
+
533
+ _EXTRA_ARGS_DEFAULT = typer.Argument(
534
+ None, help="Additional arguments to pass to the selected command"
535
+ )
530
536
 
531
537
 
532
538
  class FzfCommand(BaseCommand):
@@ -542,9 +548,7 @@ class FzfCommand(BaseCommand):
542
548
 
543
549
  def handle(
544
550
  self,
545
- extra_args: list[str] = typer.Argument(
546
- None, help="Additional arguments to pass to the selected command"
547
- ),
551
+ extra_args: list[str] = _EXTRA_ARGS_DEFAULT,
548
552
  ) -> None:
549
553
  """Handle the fzf command execution.
550
554
 
@@ -4,12 +4,8 @@ from __future__ import annotations
4
4
 
5
5
  import re
6
6
  import sys
7
- from datetime import datetime, timedelta
7
+ from datetime import datetime, timedelta, timezone
8
8
  from pathlib import Path
9
- from typing import TYPE_CHECKING
10
-
11
- if TYPE_CHECKING:
12
- pass
13
9
 
14
10
 
15
11
  class _LazyConsole:
@@ -122,7 +118,7 @@ def format_relative_time(timestamp: datetime) -> str:
122
118
  Returns:
123
119
  Relative time string (e.g., "2 hours ago", "3 days ago").
124
120
  """
125
- now = datetime.now()
121
+ now = datetime.now(tz=timezone.utc)
126
122
  diff = now - timestamp
127
123
 
128
124
  if diff < timedelta(minutes=1):
@@ -46,9 +46,7 @@ class MakeCommand(BaseCommand):
46
46
  class_name = (
47
47
  pascalcase(clean_name.replace(":", "_").replace(" ", "_")) + "Command"
48
48
  )
49
- if ":" in clean_name:
50
- command_name = clean_name
51
- elif " " in clean_name:
49
+ if ":" in clean_name or " " in clean_name:
52
50
  command_name = clean_name
53
51
  else:
54
52
  command_name = snakecase(clean_name)
@@ -48,8 +48,7 @@ class MakeThemeCommand(BaseCommand):
48
48
  themes_dir.mkdir(parents=True, exist_ok=True)
49
49
 
50
50
  base_name = Path(clean_name).name
51
- if base_name.endswith(".toml"):
52
- base_name = base_name[: -len(".toml")]
51
+ base_name = base_name.removesuffix(".toml")
53
52
  if not base_name:
54
53
  console.print(
55
54
  f"[{COLOR.ERROR}]Error: Theme name cannot be empty.[/{COLOR.ERROR}]"
@@ -19,7 +19,7 @@ from rich.prompt import Confirm, Prompt
19
19
  if sys.version_info >= (3, 11):
20
20
  import tomllib
21
21
  else:
22
- import tomli as tomllib
22
+ import tomli as tomllib # pragma: no cover - only on Python < 3.11
23
23
 
24
24
  import pyfiglet
25
25
 
@@ -328,6 +328,7 @@ class InitCommand(BaseCommand):
328
328
  capture_output=True,
329
329
  text=True,
330
330
  cwd=cwd,
331
+ check=False,
331
332
  )
332
333
  if result.returncode == 0:
333
334
  console.print(
@@ -344,6 +345,7 @@ class InitCommand(BaseCommand):
344
345
  capture_output=True,
345
346
  text=True,
346
347
  cwd=cwd,
348
+ check=False,
347
349
  )
348
350
  if result.returncode == 0:
349
351
  console.print(
@@ -393,8 +395,7 @@ class InitCommand(BaseCommand):
393
395
  value = value.strip()
394
396
  if not value:
395
397
  return None
396
- if value.startswith("#"):
397
- value = value[1:]
398
+ value = value.removeprefix("#")
398
399
  if len(value) == 3:
399
400
  value = "".join(ch * 2 for ch in value)
400
401
  if len(value) != 6:
@@ -11,11 +11,15 @@ Usage:
11
11
 
12
12
  from __future__ import annotations
13
13
 
14
+ import logging
14
15
  import os
15
16
  import sys
16
17
  import time
18
+ from collections.abc import Callable
17
19
  from pathlib import Path
18
- from typing import Any, Callable, Final, Protocol, cast, final
20
+ from typing import Any, Final, Protocol, cast, final
21
+
22
+ logger = logging.getLogger(__name__)
19
23
 
20
24
 
21
25
  def _import_tomllib():
@@ -188,8 +192,8 @@ def _get_console_script_aliases(command_name: str | None) -> set[str]:
188
192
  ep.name for ep in dist.entry_points if ep.group == "console_scripts"
189
193
  ]
190
194
  aliases.update(names)
191
- except Exception:
192
- pass
195
+ except (AttributeError, OSError):
196
+ logger.debug("Failed to read entry points for distribution %s", dist)
193
197
  return aliases
194
198
 
195
199
 
@@ -456,8 +460,7 @@ def _hex_to_rgb(value: str) -> tuple[int, int, int] | None:
456
460
  value = value.strip()
457
461
  if not value:
458
462
  return None
459
- if value.startswith("#"):
460
- value = value[1:]
463
+ value = value.removeprefix("#")
461
464
  if len(value) == 3:
462
465
  value = "".join(ch * 2 for ch in value)
463
466
  if len(value) != 6: