usecli 0.1.45__tar.gz → 0.1.47__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 (71) hide show
  1. {usecli-0.1.45 → usecli-0.1.47}/PKG-INFO +1 -1
  2. {usecli-0.1.45 → usecli-0.1.47}/pyproject.toml +1 -1
  3. {usecli-0.1.45 → usecli-0.1.47}/src/usecli/cli/commands/defaults/base/about_command.py +136 -17
  4. {usecli-0.1.45 → usecli-0.1.47}/src/usecli/cli/commands/init_command.py +52 -9
  5. {usecli-0.1.45 → usecli-0.1.47}/src/usecli/cli/config/colors.py +105 -1
  6. {usecli-0.1.45 → usecli-0.1.47}/src/usecli/usecli.config.toml +1 -1
  7. {usecli-0.1.45 → usecli-0.1.47}/LICENSE +0 -0
  8. {usecli-0.1.45 → usecli-0.1.47}/README.md +0 -0
  9. {usecli-0.1.45 → usecli-0.1.47}/src/usecli/__init__.py +0 -0
  10. {usecli-0.1.45 → usecli-0.1.47}/src/usecli/cli/__init__.py +0 -0
  11. {usecli-0.1.45 → usecli-0.1.47}/src/usecli/cli/commands/README.md +0 -0
  12. {usecli-0.1.45 → usecli-0.1.47}/src/usecli/cli/commands/__init__.py +0 -0
  13. {usecli-0.1.45 → usecli-0.1.47}/src/usecli/cli/commands/custom/README.md +0 -0
  14. {usecli-0.1.45 → usecli-0.1.47}/src/usecli/cli/commands/custom/__init__.py +0 -0
  15. {usecli-0.1.45 → usecli-0.1.47}/src/usecli/cli/commands/defaults/__init__.py +0 -0
  16. {usecli-0.1.45 → usecli-0.1.47}/src/usecli/cli/commands/defaults/base/__init__.py +0 -0
  17. {usecli-0.1.45 → usecli-0.1.47}/src/usecli/cli/commands/defaults/base/help_command.py +0 -0
  18. {usecli-0.1.45 → usecli-0.1.47}/src/usecli/cli/commands/defaults/base/inspire_command.py +0 -0
  19. {usecli-0.1.45 → usecli-0.1.47}/src/usecli/cli/commands/defaults/base/internal/__init__.py +0 -0
  20. {usecli-0.1.45 → usecli-0.1.47}/src/usecli/cli/commands/defaults/base/internal/fzf_command.py +0 -0
  21. {usecli-0.1.45 → usecli-0.1.47}/src/usecli/cli/commands/defaults/core/__init__.py +0 -0
  22. {usecli-0.1.45 → usecli-0.1.47}/src/usecli/cli/commands/defaults/core/utils.py +0 -0
  23. {usecli-0.1.45 → usecli-0.1.47}/src/usecli/cli/commands/defaults/make/__init__.py +0 -0
  24. {usecli-0.1.45 → usecli-0.1.47}/src/usecli/cli/commands/defaults/make/make_command.py +0 -0
  25. {usecli-0.1.45 → usecli-0.1.47}/src/usecli/cli/commands/defaults/make/make_theme_command.py +0 -0
  26. {usecli-0.1.45 → usecli-0.1.47}/src/usecli/cli/config/__init__.py +0 -0
  27. {usecli-0.1.45 → usecli-0.1.47}/src/usecli/cli/core/__init__.py +0 -0
  28. {usecli-0.1.45 → usecli-0.1.47}/src/usecli/cli/core/base_command.py +0 -0
  29. {usecli-0.1.45 → usecli-0.1.47}/src/usecli/cli/core/error/__init__.py +0 -0
  30. {usecli-0.1.45 → usecli-0.1.47}/src/usecli/cli/core/error/handler.py +0 -0
  31. {usecli-0.1.45 → usecli-0.1.47}/src/usecli/cli/core/error/utils.py +0 -0
  32. {usecli-0.1.45 → usecli-0.1.47}/src/usecli/cli/core/exceptions/__init__.py +0 -0
  33. {usecli-0.1.45 → usecli-0.1.47}/src/usecli/cli/core/exceptions/base.py +0 -0
  34. {usecli-0.1.45 → usecli-0.1.47}/src/usecli/cli/core/exceptions/config.py +0 -0
  35. {usecli-0.1.45 → usecli-0.1.47}/src/usecli/cli/core/exceptions/usage.py +0 -0
  36. {usecli-0.1.45 → usecli-0.1.47}/src/usecli/cli/core/exceptions/validation.py +0 -0
  37. {usecli-0.1.45 → usecli-0.1.47}/src/usecli/cli/core/skill_generator.py +0 -0
  38. {usecli-0.1.45 → usecli-0.1.47}/src/usecli/cli/core/ui/__init__.py +0 -0
  39. {usecli-0.1.45 → usecli-0.1.47}/src/usecli/cli/core/ui/list.py +0 -0
  40. {usecli-0.1.45 → usecli-0.1.47}/src/usecli/cli/core/ui/title.py +0 -0
  41. {usecli-0.1.45 → usecli-0.1.47}/src/usecli/cli/core/ui/title.txt +0 -0
  42. {usecli-0.1.45 → usecli-0.1.47}/src/usecli/cli/core/validators/__init__.py +0 -0
  43. {usecli-0.1.45 → usecli-0.1.47}/src/usecli/cli/core/validators/network.py +0 -0
  44. {usecli-0.1.45 → usecli-0.1.47}/src/usecli/cli/core/validators/numeric.py +0 -0
  45. {usecli-0.1.45 → usecli-0.1.47}/src/usecli/cli/core/validators/path.py +0 -0
  46. {usecli-0.1.45 → usecli-0.1.47}/src/usecli/cli/core/validators/string.py +0 -0
  47. {usecli-0.1.45 → usecli-0.1.47}/src/usecli/cli/services/__init__.py +0 -0
  48. {usecli-0.1.45 → usecli-0.1.47}/src/usecli/cli/services/command_service.py +0 -0
  49. {usecli-0.1.45 → usecli-0.1.47}/src/usecli/cli/templates/command.py.j2 +0 -0
  50. {usecli-0.1.45 → usecli-0.1.47}/src/usecli/cli/templates/theme.toml.j2 +0 -0
  51. {usecli-0.1.45 → usecli-0.1.47}/src/usecli/cli/templates/usecli.config.toml.j2 +0 -0
  52. {usecli-0.1.45 → usecli-0.1.47}/src/usecli/cli/themes/ayu_dark.toml +0 -0
  53. {usecli-0.1.45 → usecli-0.1.47}/src/usecli/cli/themes/catppuccin_frappe.toml +0 -0
  54. {usecli-0.1.45 → usecli-0.1.47}/src/usecli/cli/themes/catppuccin_latte.toml +0 -0
  55. {usecli-0.1.45 → usecli-0.1.47}/src/usecli/cli/themes/catppuccin_macchiato.toml +0 -0
  56. {usecli-0.1.45 → usecli-0.1.47}/src/usecli/cli/themes/catppuccin_mocha.toml +0 -0
  57. {usecli-0.1.45 → usecli-0.1.47}/src/usecli/cli/themes/default.toml +0 -0
  58. {usecli-0.1.45 → usecli-0.1.47}/src/usecli/cli/themes/dracula.toml +0 -0
  59. {usecli-0.1.45 → usecli-0.1.47}/src/usecli/cli/themes/gruvbox_dark.toml +0 -0
  60. {usecli-0.1.45 → usecli-0.1.47}/src/usecli/cli/themes/nord.toml +0 -0
  61. {usecli-0.1.45 → usecli-0.1.47}/src/usecli/cli/themes/tokyo_night.toml +0 -0
  62. {usecli-0.1.45 → usecli-0.1.47}/src/usecli/cli/utils/__init__.py +0 -0
  63. {usecli-0.1.45 → usecli-0.1.47}/src/usecli/cli/utils/interactive/__init__.py +0 -0
  64. {usecli-0.1.45 → usecli-0.1.47}/src/usecli/cli/utils/interactive/terminal_menu.py +0 -0
  65. {usecli-0.1.45 → usecli-0.1.47}/src/usecli/menu.py +0 -0
  66. {usecli-0.1.45 → usecli-0.1.47}/src/usecli/params.py +0 -0
  67. {usecli-0.1.45 → usecli-0.1.47}/src/usecli/shared/__init__.py +0 -0
  68. {usecli-0.1.45 → usecli-0.1.47}/src/usecli/shared/config/__init__.py +0 -0
  69. {usecli-0.1.45 → usecli-0.1.47}/src/usecli/shared/config/globals.py +0 -0
  70. {usecli-0.1.45 → usecli-0.1.47}/src/usecli/shared/config/manager.py +0 -0
  71. {usecli-0.1.45 → usecli-0.1.47}/src/usecli/ui.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: usecli
3
- Version: 0.1.45
3
+ Version: 0.1.47
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>
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "usecli"
3
- version = "0.1.45"
3
+ version = "0.1.47"
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" }]
@@ -1,5 +1,7 @@
1
1
  from __future__ import annotations
2
2
 
3
+ import importlib.metadata
4
+ import os
3
5
  import platform
4
6
  import re
5
7
  import sys
@@ -55,7 +57,51 @@ def _parse_dependency_requirement(req: str) -> tuple[str, str | None]:
55
57
  return name, remainder
56
58
 
57
59
 
60
+ def _get_console_script_distribution(
61
+ command_name: str | None,
62
+ ) -> importlib.metadata.Distribution | None:
63
+ if not command_name:
64
+ return None
65
+ try:
66
+ distributions = importlib.metadata.distributions()
67
+ except Exception:
68
+ return None
69
+ for dist in distributions:
70
+ try:
71
+ entry_points = dist.entry_points
72
+ except Exception:
73
+ continue
74
+ for entry_point in entry_points:
75
+ if entry_point.group != "console_scripts":
76
+ continue
77
+ if entry_point.name == command_name:
78
+ return dist
79
+ return None
80
+
81
+
82
+ def _get_package_dependencies_from_distribution(
83
+ dist: importlib.metadata.Distribution,
84
+ ) -> list[tuple[str, str | None]]:
85
+ requires = dist.requires or []
86
+ result: list[tuple[str, str | None]] = []
87
+ for req in requires:
88
+ if not isinstance(req, str):
89
+ continue
90
+ name, spec = _parse_dependency_requirement(req)
91
+ if name and not req.startswith("("):
92
+ result.append((name, spec))
93
+ return result
94
+
95
+
58
96
  def _get_dependencies(config: ConfigManager) -> list[tuple[str, str | None]]:
97
+ command_name = os.path.basename(sys.argv[0]) if sys.argv else None
98
+ dist = _get_console_script_distribution(command_name)
99
+ if dist is None:
100
+ primary_command = get_script_command_name(default=None)
101
+ dist = _get_console_script_distribution(primary_command)
102
+ if dist is not None:
103
+ return _get_package_dependencies_from_distribution(dist)
104
+
59
105
  pyproject_path = config.pyproject_path
60
106
  if not pyproject_path.exists():
61
107
  return []
@@ -79,8 +125,91 @@ def _get_dependencies(config: ConfigManager) -> list[tuple[str, str | None]]:
79
125
  return result
80
126
 
81
127
 
128
+ def _get_application_distribution() -> importlib.metadata.Distribution | None:
129
+ command_name = os.path.basename(sys.argv[0]) if sys.argv else None
130
+ dist = _get_console_script_distribution(command_name)
131
+ if dist is None:
132
+ primary_command = get_script_command_name(default=None)
133
+ dist = _get_console_script_distribution(primary_command)
134
+ return dist
135
+
136
+
137
+ def _get_application_version(config: ConfigManager) -> str:
138
+ dist = _get_application_distribution()
139
+ if dist is not None:
140
+ return dist.version
141
+
142
+ config_version = config.get_project_version()
143
+ if config_version:
144
+ return config_version
145
+
146
+ return _get_version()
147
+
148
+
149
+ def _get_application_description(config: ConfigManager) -> str:
150
+ description = config.get("description")
151
+ if (
152
+ config.has_key("description")
153
+ and isinstance(description, str)
154
+ and description.strip()
155
+ ):
156
+ return description.strip()
157
+
158
+ project_description = _get_project_description(config)
159
+ if project_description:
160
+ return project_description
161
+
162
+ return (
163
+ "An elegant CLI framework for Python with prefix matching, "
164
+ "rich UI, and command scaffolding."
165
+ )
166
+
167
+
168
+ def _get_project_description(config: ConfigManager) -> str | None:
169
+ pyproject_path = config.pyproject_path
170
+ if not pyproject_path.exists():
171
+ return None
172
+
173
+ try:
174
+ data = tomllib.loads(pyproject_path.read_text())
175
+ except (tomllib.TOMLDecodeError, OSError):
176
+ return None
177
+
178
+ description = data.get("project", {}).get("description")
179
+ if isinstance(description, str) and description.strip():
180
+ return description.strip()
181
+ return None
182
+
183
+
184
+ def _get_installed_script_commands(command_name: str | None) -> list[str]:
185
+ dist = _get_console_script_distribution(command_name)
186
+ if dist is None:
187
+ return []
188
+ try:
189
+ entry_points = dist.entry_points
190
+ except Exception:
191
+ return []
192
+ script_names = [
193
+ entry_point.name
194
+ for entry_point in entry_points
195
+ if entry_point.group == "console_scripts"
196
+ ]
197
+ if not script_names:
198
+ return []
199
+ if command_name and command_name in script_names:
200
+ return [command_name, *[name for name in script_names if name != command_name]]
201
+ return script_names
202
+
203
+
82
204
  def _get_script_commands() -> list[str]:
83
205
  primary_command = get_script_command_name(default=None)
206
+ command_name = os.path.basename(sys.argv[0]) if sys.argv else primary_command
207
+ installed_commands = _get_installed_script_commands(command_name)
208
+ if installed_commands:
209
+ if primary_command and primary_command not in installed_commands:
210
+ return [primary_command, *installed_commands]
211
+ return installed_commands
212
+
84
213
  pyproject_path = Path.cwd() / "pyproject.toml"
85
214
  if not pyproject_path.exists():
86
215
  if primary_command:
@@ -115,19 +244,9 @@ class AboutCommand(BaseCommand):
115
244
 
116
245
  def handle(self) -> None:
117
246
  config = get_config()
118
- version = config.get_project_version() or _get_version()
247
+ version = _get_application_version(config)
119
248
  app_name = get_project_name()
120
- description = config.get("description")
121
- if not (
122
- config.has_key("description")
123
- and isinstance(description, str)
124
- and description.strip()
125
- ):
126
- description = (
127
- "An elegant CLI framework for Python with prefix matching, "
128
- "rich UI, and command scaffolding."
129
- )
130
- description = description.strip() if isinstance(description, str) else ""
249
+ description = _get_application_description(config)
131
250
 
132
251
  console.print()
133
252
  console.print(f"[bold {COLOR.PRIMARY}]Description[/bold {COLOR.PRIMARY}]")
@@ -138,8 +257,11 @@ class AboutCommand(BaseCommand):
138
257
  console.print(f"[bold {COLOR.PRIMARY}]Environment[/bold {COLOR.PRIMARY}]")
139
258
  console.print(f"[{COLOR.PRIMARY}]─" * 78)
140
259
 
141
- self._print_row("Application Name", app_name)
142
- self._print_row("Application Version", version)
260
+ dist = _get_application_distribution()
261
+ name_label = "Cli Name" if dist is not None else "Application Name"
262
+ version_label = "Cli Version" if dist is not None else "Application Version"
263
+ self._print_row(name_label, app_name)
264
+ self._print_row(version_label, version)
143
265
  self._print_row("Python Version", platform.python_version())
144
266
  self._print_row("Platform", f"[{COLOR.FOREGROUND_MUTED}]{platform.platform()}")
145
267
 
@@ -159,9 +281,6 @@ class AboutCommand(BaseCommand):
159
281
  deps = _get_dependencies(config)
160
282
  if deps:
161
283
  for dep_name, spec in deps:
162
- if dep_name == "usecli" and spec:
163
- self._print_row(dep_name, spec)
164
- continue
165
284
  try:
166
285
  installed_version = get_version(dep_name)
167
286
  self._print_row(dep_name, installed_version)
@@ -108,7 +108,17 @@ class InitCommand(BaseCommand):
108
108
  return "updated" if existed else "created"
109
109
 
110
110
  def _should_skip_config_path(self, path: Path) -> bool:
111
- return any(part in ConfigManager._SKIP_DIRS for part in path.parts)
111
+ try:
112
+ resolved = path.resolve()
113
+ except OSError:
114
+ resolved = path
115
+ if any(part in ConfigManager._SKIP_DIRS for part in resolved.parts):
116
+ return True
117
+ try:
118
+ resolved.relative_to(Path(sys.prefix).resolve())
119
+ except ValueError:
120
+ return False
121
+ return True
112
122
 
113
123
  def _resolve_config_path(self, value: str, project_root: Path) -> Path:
114
124
  path = Path(value).expanduser()
@@ -186,6 +196,36 @@ class InitCommand(BaseCommand):
186
196
 
187
197
  return created
188
198
 
199
+ def _find_project_root_for_init(self, start_dir: Path) -> Path:
200
+ current = start_dir.resolve()
201
+ git_root: Path | None = None
202
+ while True:
203
+ if (current / "pyproject.toml").exists():
204
+ return current
205
+ if (current / USECLI_CONFIG_TOML).exists():
206
+ return current
207
+ git_dir = current / ".git"
208
+ if git_dir.exists():
209
+ git_root = current
210
+ break
211
+ parent = current.parent
212
+ if parent == current:
213
+ break
214
+ current = parent
215
+ return (git_root or start_dir).resolve()
216
+
217
+ def _find_pyproject_path_for_init(self, start_dir: Path) -> Path | None:
218
+ current = start_dir.resolve()
219
+ while True:
220
+ pyproject_path = current / "pyproject.toml"
221
+ if pyproject_path.exists():
222
+ return pyproject_path
223
+ parent = current.parent
224
+ if parent == current:
225
+ break
226
+ current = parent
227
+ return None
228
+
189
229
  def _derive_templates_dir(self, commands_dir: str) -> str:
190
230
  commands_path = Path(commands_dir)
191
231
  parent = commands_path.parent
@@ -509,12 +549,9 @@ include = ["{root_package}*"]
509
549
  ),
510
550
  ) -> None:
511
551
  cwd = Path.cwd()
512
- config_manager = ConfigManager(start_dir=cwd)
513
- project_root = config_manager.get_project_root()
514
- pyproject_path = (
515
- config_manager.pyproject_path
516
- if config_manager.pyproject_path.exists()
517
- else project_root / "pyproject.toml"
552
+ project_root = self._find_project_root_for_init(cwd)
553
+ pyproject_path = self._find_pyproject_path_for_init(cwd) or (
554
+ project_root / "pyproject.toml"
518
555
  )
519
556
 
520
557
  console.print()
@@ -712,14 +749,20 @@ include = ["{root_package}*"]
712
749
  config_root = project_root
713
750
  if commands_path.parent != project_root:
714
751
  config_root = commands_path.parent
715
- existing_config = ConfigManager(start_dir=config_root).usecli_config_path
716
- if existing_config.exists() and self._should_skip_config_path(existing_config):
752
+ existing_config = ConfigManager._find_usecli_config_in_tree(
753
+ project_root,
754
+ config_root,
755
+ skip_venv=True,
756
+ )
757
+ if existing_config is None or self._should_skip_config_path(existing_config):
717
758
  existing_config = config_root / USECLI_CONFIG_TOML
718
759
  default_config_path = (
719
760
  existing_config
720
761
  if existing_config.exists()
721
762
  else config_root / USECLI_CONFIG_TOML
722
763
  )
764
+ if self._should_skip_config_path(default_config_path):
765
+ default_config_path = config_root / USECLI_CONFIG_TOML
723
766
  config_location = Prompt.ask(
724
767
  f"[bold {COLOR.SECONDARY}]Config file location[/bold {COLOR.SECONDARY}]"
725
768
  " (path or directory)",
@@ -100,6 +100,110 @@ def _find_usecli_config_path(
100
100
  return selection[0]
101
101
 
102
102
 
103
+ def _get_command_name() -> str | None:
104
+ """Get the current command name from sys.argv."""
105
+ if not sys.argv:
106
+ return None
107
+ command = os.path.basename(sys.argv[0])
108
+ return command if command else None
109
+
110
+
111
+ def _get_console_script_aliases(command_name: str | None) -> set[str]:
112
+ """Get all aliases for a console script from package metadata."""
113
+ if not command_name:
114
+ return set()
115
+ aliases: set[str] = {command_name}
116
+ try:
117
+ distributions = importlib.metadata.distributions()
118
+ except Exception:
119
+ return aliases
120
+ for dist in distributions:
121
+ try:
122
+ entry_points = dist.entry_points
123
+ except Exception:
124
+ continue
125
+ names = [
126
+ entry_point.name
127
+ for entry_point in entry_points
128
+ if entry_point.group == "console_scripts"
129
+ ]
130
+ if command_name in names:
131
+ aliases.update(names)
132
+ break
133
+ return aliases
134
+
135
+
136
+ def _config_matches_command(path: Path, command_name: str | None) -> bool:
137
+ """Check if a config file matches the given command name."""
138
+ if command_name is None:
139
+ return True
140
+ try:
141
+ data = _load_usecli_config_file(path)
142
+ except (tomllib.TOMLDecodeError, OSError):
143
+ return True
144
+ config_command = data.get("command_name")
145
+ if not isinstance(config_command, str):
146
+ return True
147
+ normalized = config_command.strip()
148
+ if not normalized:
149
+ return True
150
+ if normalized == command_name:
151
+ return True
152
+ aliases = _get_console_script_aliases(command_name)
153
+ return normalized in aliases
154
+
155
+
156
+ def _find_usecli_config_path_for_command(
157
+ root_dir: Path, start_dir: Path, *, skip_venv: bool
158
+ ) -> Path | None:
159
+ """Find usecli config that matches the current command."""
160
+ if not root_dir.exists() or not root_dir.is_dir():
161
+ return None
162
+
163
+ candidates = [path for path in root_dir.rglob(USECLI_CONFIG_TOML)]
164
+ if skip_venv:
165
+ candidates = [
166
+ path
167
+ for path in candidates
168
+ if not any(part in _SKIP_DIRS for part in path.parts)
169
+ ]
170
+ if not candidates:
171
+ return None
172
+
173
+ command_name = _get_command_name()
174
+
175
+ # Filter candidates by command_name matching
176
+ if command_name:
177
+ candidates = [
178
+ path for path in candidates if _config_matches_command(path, command_name)
179
+ ]
180
+
181
+ if not candidates:
182
+ return None
183
+
184
+ start_dir = start_dir.resolve()
185
+ preferred: list[Path] = []
186
+ for path in candidates:
187
+ try:
188
+ path.relative_to(start_dir)
189
+ preferred.append(path)
190
+ except ValueError:
191
+ continue
192
+
193
+ selection = preferred or candidates
194
+
195
+ def _depth_key(path: Path) -> tuple[int, str]:
196
+ try:
197
+ relative = path.relative_to(start_dir)
198
+ return (len(relative.parts), str(path))
199
+ except ValueError:
200
+ relative = path.relative_to(root_dir)
201
+ return (len(relative.parts), str(path))
202
+
203
+ selection.sort(key=_depth_key)
204
+ return selection[0]
205
+
206
+
103
207
  def _find_usecli_config_in_package() -> Path | None:
104
208
  spec = importlib.util.find_spec(_get_package_name())
105
209
  if spec is None or not spec.submodule_search_locations:
@@ -242,7 +346,7 @@ def _load_usecli_config(
242
346
 
243
347
  config_path = project_root / USECLI_CONFIG_TOML
244
348
  if not config_path.exists():
245
- config_path = _find_usecli_config_path(
349
+ config_path = _find_usecli_config_path_for_command(
246
350
  project_root,
247
351
  project_root,
248
352
  skip_venv=True,
@@ -3,7 +3,7 @@ command_name = "usecli"
3
3
  title = "usecli"
4
4
  title_file = "cli/core/ui/title.txt"
5
5
  title_font = "ansi_shadow"
6
- description = "A custom CLI tool"
6
+ description = "A powerful Python CLI framework for building beautiful, developer-friendly command-line tools."
7
7
  commands_dir = "cli/commands/custom"
8
8
  templates_dir = "cli/templates"
9
9
  themes_dir = "cli/themes"
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes